feat: splitting platforms and maybe fixing shortcut installations as well as admin privileges
This commit is contained in:
@@ -2,4 +2,9 @@ module git.mypapercloud.de/fierke/logofclient-installer
|
|||||||
|
|
||||||
go 1.26.8
|
go 1.26.8
|
||||||
|
|
||||||
require github.com/ulikunitz/xz v0.5.17
|
require (
|
||||||
|
github.com/go-ole/go-ole v1.3.0
|
||||||
|
github.com/ulikunitz/xz v0.5.17
|
||||||
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.1.0 // indirect
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
|
github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4=
|
||||||
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
||||||
|
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -18,11 +17,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
appName = "logofclient"
|
appName = "logofclient"
|
||||||
appVersion = "1.0.0"
|
appVersion = "1.0.0"
|
||||||
defaultGitea = "https://git.mypapercloud.de"
|
defaultGitea = "https://git.mypapercloud.de"
|
||||||
defaultOwner = "fierke"
|
defaultOwner = "fierke"
|
||||||
defaultRepo = "logofclient"
|
defaultRepo = "logofclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -74,33 +73,15 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "-v", "--version":
|
|
||||||
fmt.Printf("%s %s\n", appName, appVersion)
|
|
||||||
|
|
||||||
case "-h", "--help", "help":
|
|
||||||
printHelp()
|
|
||||||
|
|
||||||
case "-y":
|
case "-y":
|
||||||
if len(args) < 2 {
|
if len(args) != 2 {
|
||||||
printHelp()
|
printHelp()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "install":
|
case "install", "update", "uninstall":
|
||||||
if err := runPrivileged(config, "install"); err != nil {
|
if err := runPrivileged(config, args[1]); err != nil {
|
||||||
printError(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "update":
|
|
||||||
if err := runPrivileged(config, "update"); err != nil {
|
|
||||||
printError(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "uninstall":
|
|
||||||
if err := runPrivileged(config, "uninstall"); err != nil {
|
|
||||||
printError(err)
|
printError(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -110,6 +91,12 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "-v", "--version":
|
||||||
|
fmt.Printf("%s %s\n", appName, appVersion)
|
||||||
|
|
||||||
|
case "-h", "--help", "help":
|
||||||
|
printHelp()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmt.Printf("Unbekannter Befehl: %s\n\n", args[0])
|
fmt.Printf("Unbekannter Befehl: %s\n\n", args[0])
|
||||||
printHelp()
|
printHelp()
|
||||||
@@ -137,14 +124,14 @@ func runTUI(config Config) {
|
|||||||
fmt.Print("Auswahl: ")
|
fmt.Print("Auswahl: ")
|
||||||
|
|
||||||
var choice string
|
var choice string
|
||||||
|
|
||||||
if _, err := fmt.Scanln(&choice); err != nil {
|
if _, err := fmt.Scanln(&choice); err != nil {
|
||||||
fmt.Println()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch choice {
|
switch strings.TrimSpace(choice) {
|
||||||
case "1":
|
case "1":
|
||||||
err = runPrivileged(config, "install")
|
err = runPrivileged(config, "install")
|
||||||
|
|
||||||
@@ -184,7 +171,7 @@ Verwendung:
|
|||||||
Optionen:
|
Optionen:
|
||||||
-h, --help Diese Hilfe anzeigen
|
-h, --help Diese Hilfe anzeigen
|
||||||
-v, --version Version des Installers anzeigen
|
-v, --version Version des Installers anzeigen
|
||||||
-y <command> Befehl ohne TUI ausführen
|
-y <Befehl> Befehl ohne interaktive Abfrage ausführen
|
||||||
|
|
||||||
Beispiele:
|
Beispiele:
|
||||||
logofclient-installer install
|
logofclient-installer install
|
||||||
@@ -193,60 +180,37 @@ Beispiele:
|
|||||||
logofclient-installer -y install`)
|
logofclient-installer -y install`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Privileges
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
func runPrivileged(config Config, action string) error {
|
|
||||||
// Windows:
|
|
||||||
// Die EXE wird später über ein Manifest mit
|
|
||||||
// requestedExecutionLevel="requireAdministrator"
|
|
||||||
// gestartet. Hier ist daher keine Elevation nötig.
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
return runAction(config, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linux: bereits root
|
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
return runAction(config, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linux: mit sudo neu starten
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("Für diese Aktion werden Administratorrechte benötigt.")
|
|
||||||
fmt.Println("Starte Installer mit sudo...")
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Installer-Pfad konnte nicht ermittelt werden: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("sudo", exe, action)
|
|
||||||
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runAction(config Config, action string) error {
|
func runAction(config Config, action string) error {
|
||||||
switch action {
|
switch action {
|
||||||
case "install":
|
case "install":
|
||||||
return install(config)
|
return install(config)
|
||||||
|
|
||||||
case "update":
|
case "update":
|
||||||
return update(config)
|
return update(config)
|
||||||
|
|
||||||
case "uninstall":
|
case "uninstall":
|
||||||
return uninstall(config)
|
return uninstall(config)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unbekannte Aktion: %s", action)
|
return fmt.Errorf("unbekannte Aktion: %s", action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Privilegien
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
// runPrivileged wird von den Plattformdateien implementiert.
|
||||||
|
//
|
||||||
|
// Linux:
|
||||||
|
// - fragt nach Bestätigung
|
||||||
|
// - startet sich bei Bedarf über sudo neu
|
||||||
|
//
|
||||||
|
// Windows:
|
||||||
|
// - die EXE wird über das Manifest mit
|
||||||
|
// requireAdministrator gestartet
|
||||||
|
// - hier ist keine weitere Elevation nötig
|
||||||
|
func runPrivileged(config Config, action string) error {
|
||||||
|
return platformRunPrivileged(config, action)
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Installation
|
// Installation
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
@@ -260,6 +224,7 @@ func install(config Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tag := release.TagName
|
tag := release.TagName
|
||||||
|
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
return errors.New("Release enthält keinen Tag")
|
return errors.New("Release enthält keinen Tag")
|
||||||
}
|
}
|
||||||
@@ -268,6 +233,13 @@ func install(config Config) error {
|
|||||||
|
|
||||||
assetName := getAssetName(tag)
|
assetName := getAssetName(tag)
|
||||||
|
|
||||||
|
if assetName == "" {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"kein Paket für Betriebssystem %s definiert",
|
||||||
|
runtime.GOOS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
asset, err := findAsset(release, assetName)
|
asset, err := findAsset(release, assetName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -277,7 +249,10 @@ func install(config Config) error {
|
|||||||
|
|
||||||
tempDir, err := os.MkdirTemp("", "logofclient-installer-*")
|
tempDir, err := os.MkdirTemp("", "logofclient-installer-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("temporäres Verzeichnis konnte nicht erstellt werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"temporäres Verzeichnis konnte nicht erstellt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tempDir)
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
@@ -294,16 +269,20 @@ func install(config Config) error {
|
|||||||
|
|
||||||
fmt.Printf("Installing to: %s\n", installDir)
|
fmt.Printf("Installing to: %s\n", installDir)
|
||||||
|
|
||||||
// Alte Installation entfernen
|
|
||||||
if err := removeIfExists(installDir); err != nil {
|
if err := removeIfExists(installDir); err != nil {
|
||||||
return fmt.Errorf("alte Installation konnte nicht entfernt werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"alte Installation konnte nicht entfernt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(installDir, 0755); err != nil {
|
if err := os.MkdirAll(installDir, 0755); err != nil {
|
||||||
return fmt.Errorf("Installationsverzeichnis konnte nicht erstellt werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"Installationsverzeichnis konnte nicht erstellt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Archiv entpacken
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
if err := extractTarXz(archivePath, installDir); err != nil {
|
if err := extractTarXz(archivePath, installDir); err != nil {
|
||||||
@@ -316,10 +295,12 @@ func install(config Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("nicht unterstütztes Betriebssystem: %s", runtime.GOOS)
|
return fmt.Errorf(
|
||||||
|
"nicht unterstütztes Betriebssystem: %s",
|
||||||
|
runtime.GOOS,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// installierte Version speichern
|
|
||||||
versionFile := filepath.Join(installDir, ".version")
|
versionFile := filepath.Join(installDir, ".version")
|
||||||
|
|
||||||
if err := os.WriteFile(
|
if err := os.WriteFile(
|
||||||
@@ -327,24 +308,22 @@ func install(config Config) error {
|
|||||||
[]byte(tag+"\n"),
|
[]byte(tag+"\n"),
|
||||||
0644,
|
0644,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("Version konnte nicht gespeichert werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"Version konnte nicht gespeichert werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plattformintegration
|
if err := platformInstallIntegration(installDir); err != nil {
|
||||||
switch runtime.GOOS {
|
return err
|
||||||
case "linux":
|
|
||||||
if err := installLinuxIntegration(installDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case "windows":
|
|
||||||
if err := installWindowsIntegration(installDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("%s %s wurde erfolgreich installiert.\n", appName, tag)
|
fmt.Printf(
|
||||||
|
"%s %s wurde erfolgreich installiert.\n",
|
||||||
|
appName,
|
||||||
|
tag,
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -369,7 +348,10 @@ func update(config Config) error {
|
|||||||
return install(config)
|
return install(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("installierte Version konnte nicht gelesen werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"installierte Version konnte nicht gelesen werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVersion := strings.TrimSpace(string(currentVersionData))
|
currentVersion := strings.TrimSpace(string(currentVersionData))
|
||||||
@@ -383,8 +365,15 @@ func update(config Config) error {
|
|||||||
|
|
||||||
latestVersion := release.TagName
|
latestVersion := release.TagName
|
||||||
|
|
||||||
fmt.Printf("Installierte Version: %s\n", valueOrNone(currentVersion))
|
fmt.Printf(
|
||||||
fmt.Printf("Neueste Version: %s\n", valueOrNone(latestVersion))
|
"Installierte Version: %s\n",
|
||||||
|
valueOrNone(currentVersion),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"Neueste Version: %s\n",
|
||||||
|
valueOrNone(latestVersion),
|
||||||
|
)
|
||||||
|
|
||||||
if currentVersion == latestVersion {
|
if currentVersion == latestVersion {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -393,18 +382,21 @@ func update(config Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("Update von %s auf %s...\n", currentVersion, latestVersion)
|
fmt.Printf(
|
||||||
|
"Update von %s auf %s...\n",
|
||||||
|
currentVersion,
|
||||||
|
latestVersion,
|
||||||
|
)
|
||||||
|
|
||||||
// install() entfernt die alte Version selbst.
|
|
||||||
return install(config)
|
return install(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Uninstall
|
// Deinstallation
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
func uninstall(config Config) error {
|
func uninstall(config Config) error {
|
||||||
_ = config // aktuell nicht benötigt
|
_ = config
|
||||||
|
|
||||||
installDir, err := getInstallDir()
|
installDir, err := getInstallDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -418,20 +410,15 @@ func uninstall(config Config) error {
|
|||||||
|
|
||||||
fmt.Printf("Deinstalliere %s...\n", appName)
|
fmt.Printf("Deinstalliere %s...\n", appName)
|
||||||
|
|
||||||
switch runtime.GOOS {
|
if err := platformRemoveIntegration(); err != nil {
|
||||||
case "linux":
|
return err
|
||||||
if err := removeLinuxIntegration(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case "windows":
|
|
||||||
if err := removeWindowsIntegration(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := removeIfExists(installDir); err != nil {
|
if err := removeIfExists(installDir); err != nil {
|
||||||
return fmt.Errorf("Installation konnte nicht entfernt werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"Installation konnte nicht entfernt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Logof Client wurde erfolgreich deinstalliert.")
|
fmt.Println("Logof Client wurde erfolgreich deinstalliert.")
|
||||||
@@ -453,12 +440,17 @@ func getLatestRelease(config Config) (*Release, error) {
|
|||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Gitea Release konnte nicht abgerufen werden: %w", err)
|
return nil, fmt.Errorf(
|
||||||
|
"Gitea Release konnte nicht abgerufen werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
body, _ := io.ReadAll(
|
||||||
|
io.LimitReader(resp.Body, 4096),
|
||||||
|
)
|
||||||
|
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"Gitea API antwortete mit HTTP %d: %s",
|
"Gitea API antwortete mit HTTP %d: %s",
|
||||||
@@ -470,7 +462,10 @@ func getLatestRelease(config Config) (*Release, error) {
|
|||||||
var release Release
|
var release Release
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||||
return nil, fmt.Errorf("Gitea-Antwort konnte nicht gelesen werden: %w", err)
|
return nil, fmt.Errorf(
|
||||||
|
"Gitea-Antwort konnte nicht gelesen werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &release, nil
|
return &release, nil
|
||||||
@@ -493,10 +488,18 @@ func findAsset(release *Release, name string) (*Asset, error) {
|
|||||||
func getAssetName(tag string) string {
|
func getAssetName(tag string) string {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
return fmt.Sprintf("%s-%s-linux.tar.xz", appName, tag)
|
return fmt.Sprintf(
|
||||||
|
"%s-%s-linux.tar.xz",
|
||||||
|
appName,
|
||||||
|
tag,
|
||||||
|
)
|
||||||
|
|
||||||
case "windows":
|
case "windows":
|
||||||
return fmt.Sprintf("%s-%s-win.zip", appName, tag)
|
return fmt.Sprintf(
|
||||||
|
"%s-%s-win.zip",
|
||||||
|
appName,
|
||||||
|
tag,
|
||||||
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
@@ -506,7 +509,10 @@ func getAssetName(tag string) string {
|
|||||||
func download(url string, destination string) error {
|
func download(url string, destination string) error {
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Download fehlgeschlagen: %w", err)
|
return fmt.Errorf(
|
||||||
|
"Download fehlgeschlagen: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -519,84 +525,16 @@ func download(url string, destination string) error {
|
|||||||
|
|
||||||
file, err := os.Create(destination)
|
file, err := os.Create(destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Download-Datei konnte nicht erstellt werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"Download-Datei konnte nicht erstellt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
if _, err := io.Copy(file, resp.Body); err != nil {
|
if _, err := io.Copy(file, resp.Body); err != nil {
|
||||||
return fmt.Errorf("Download fehlgeschlagen: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Linux
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
func getInstallDir() (string, error) {
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux":
|
|
||||||
return "/opt/logofclient", nil
|
|
||||||
|
|
||||||
case "windows":
|
|
||||||
programFiles := os.Getenv("ProgramFiles")
|
|
||||||
|
|
||||||
if programFiles == "" {
|
|
||||||
return "", errors.New("ProgramFiles ist nicht gesetzt")
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(programFiles, "logofclient"), nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("nicht unterstütztes Betriebssystem: %s", runtime.GOOS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func installLinuxIntegration(installDir string) error {
|
|
||||||
binaryPath := filepath.Join(installDir, "Logof Client")
|
|
||||||
linkPath := "/usr/local/bin/logofclient"
|
|
||||||
|
|
||||||
fmt.Println("Creating command symlink...")
|
|
||||||
|
|
||||||
if err := removeIfExists(linkPath); err != nil {
|
|
||||||
return fmt.Errorf("alter Symlink konnte nicht entfernt werden: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Symlink(binaryPath, linkPath); err != nil {
|
|
||||||
return fmt.Errorf("Symlink konnte nicht erstellt werden: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
desktopDir := "/usr/share/applications"
|
|
||||||
desktopFile := filepath.Join(desktopDir, "logofclient.desktop")
|
|
||||||
|
|
||||||
if err := os.MkdirAll(desktopDir, 0755); err != nil {
|
|
||||||
return fmt.Errorf("Desktop-Verzeichnis konnte nicht erstellt werden: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iconPath := filepath.Join(
|
|
||||||
installDir,
|
|
||||||
"assets",
|
|
||||||
"icon.png",
|
|
||||||
)
|
|
||||||
|
|
||||||
desktopEntry := fmt.Sprintf(`[Desktop Entry]
|
|
||||||
Name=Logof Client
|
|
||||||
Comment=Logof Client
|
|
||||||
Exec=%s
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Icon=%s
|
|
||||||
Categories=Office;
|
|
||||||
`, linkPath, iconPath)
|
|
||||||
|
|
||||||
if err := os.WriteFile(
|
|
||||||
desktopFile,
|
|
||||||
[]byte(desktopEntry),
|
|
||||||
0644,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Desktop-Datei konnte nicht geschrieben werden: %w",
|
"Download fehlgeschlagen: %w",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -604,135 +542,30 @@ Categories=Office;
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeLinuxIntegration() error {
|
|
||||||
linkPath := "/usr/local/bin/logofclient"
|
|
||||||
desktopFile := "/usr/share/applications/logofclient.desktop"
|
|
||||||
|
|
||||||
if err := removeIfExists(linkPath); err != nil {
|
|
||||||
return fmt.Errorf("Symlink konnte nicht entfernt werden: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := removeIfExists(desktopFile); err != nil {
|
|
||||||
return fmt.Errorf("Desktop-Datei konnte nicht entfernt werden: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Windows
|
// Archive
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
func installWindowsIntegration(installDir string) error {
|
func extractTarXz(
|
||||||
startMenu := filepath.Join(
|
archivePath string,
|
||||||
os.Getenv("ProgramData"),
|
destination string,
|
||||||
"Microsoft",
|
) error {
|
||||||
"Windows",
|
|
||||||
"Start Menu",
|
|
||||||
"Programs",
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := os.MkdirAll(startMenu, 0755); err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Startmenü-Verzeichnis konnte nicht erstellt werden: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
shortcut := filepath.Join(
|
|
||||||
startMenu,
|
|
||||||
"Logof Client.lnk",
|
|
||||||
)
|
|
||||||
|
|
||||||
target := filepath.Join(
|
|
||||||
installDir,
|
|
||||||
"Logof Client.exe",
|
|
||||||
)
|
|
||||||
|
|
||||||
icon := filepath.Join(
|
|
||||||
installDir,
|
|
||||||
"assets",
|
|
||||||
"icon.ico",
|
|
||||||
)
|
|
||||||
|
|
||||||
// PowerShell wird hier nur intern zur Erstellung der Windows-
|
|
||||||
// Shell-Verknüpfung verwendet. Der Benutzer muss kein
|
|
||||||
// PowerShell-Skript ausführen.
|
|
||||||
script := `
|
|
||||||
$ws = New-Object -ComObject WScript.Shell
|
|
||||||
$sc = $ws.CreateShortcut($args[0])
|
|
||||||
$sc.TargetPath = $args[1]
|
|
||||||
$sc.WorkingDirectory = $args[2]
|
|
||||||
$sc.Description = "Logof Client"
|
|
||||||
if (Test-Path $args[3]) {
|
|
||||||
$sc.IconLocation = $args[3]
|
|
||||||
}
|
|
||||||
$sc.Save()
|
|
||||||
`
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
|
||||||
"powershell.exe",
|
|
||||||
"-NoProfile",
|
|
||||||
"-NonInteractive",
|
|
||||||
"-Command",
|
|
||||||
script,
|
|
||||||
shortcut,
|
|
||||||
target,
|
|
||||||
installDir,
|
|
||||||
icon,
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Startmenü-Verknüpfung konnte nicht erstellt werden: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeWindowsIntegration() error {
|
|
||||||
startMenu := filepath.Join(
|
|
||||||
os.Getenv("ProgramData"),
|
|
||||||
"Microsoft",
|
|
||||||
"Windows",
|
|
||||||
"Start Menu",
|
|
||||||
"Programs",
|
|
||||||
)
|
|
||||||
|
|
||||||
shortcut := filepath.Join(
|
|
||||||
startMenu,
|
|
||||||
"Logof Client.lnk",
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := removeIfExists(shortcut); err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Startmenü-Verknüpfung konnte nicht entfernt werden: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// Archive extraction
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
func extractTarXz(archivePath string, destination string) error {
|
|
||||||
file, err := os.Open(archivePath)
|
file, err := os.Open(archivePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Archiv konnte nicht geöffnet werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"Archiv konnte nicht geöffnet werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
xzReader, err := xz.NewReader(file)
|
xzReader, err := xz.NewReader(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("XZ-Archiv konnte nicht gelesen werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"XZ-Archiv konnte nicht gelesen werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
tarReader := tar.NewReader(xzReader)
|
tarReader := tar.NewReader(xzReader)
|
||||||
@@ -745,17 +578,26 @@ func extractTarXz(archivePath string, destination string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("TAR-Archiv konnte nicht gelesen werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"TAR-Archiv konnte nicht gelesen werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := safeJoin(destination, header.Name)
|
target, err := safeJoin(
|
||||||
|
destination,
|
||||||
|
header.Name,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch header.Typeflag {
|
switch header.Typeflag {
|
||||||
case tar.TypeDir:
|
case tar.TypeDir:
|
||||||
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
|
if err := os.MkdirAll(
|
||||||
|
target,
|
||||||
|
os.FileMode(header.Mode),
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Verzeichnis %s konnte nicht erstellt werden: %w",
|
"Verzeichnis %s konnte nicht erstellt werden: %w",
|
||||||
target,
|
target,
|
||||||
@@ -764,7 +606,10 @@ func extractTarXz(archivePath string, destination string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case tar.TypeReg:
|
case tar.TypeReg:
|
||||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
if err := os.MkdirAll(
|
||||||
|
filepath.Dir(target),
|
||||||
|
0755,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,7 +626,11 @@ func extractTarXz(archivePath string, destination string) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, copyErr := io.Copy(out, tarReader)
|
_, copyErr := io.Copy(
|
||||||
|
out,
|
||||||
|
tarReader,
|
||||||
|
)
|
||||||
|
|
||||||
closeErr := out.Close()
|
closeErr := out.Close()
|
||||||
|
|
||||||
if copyErr != nil {
|
if copyErr != nil {
|
||||||
@@ -797,7 +646,10 @@ func extractTarXz(archivePath string, destination string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case tar.TypeSymlink:
|
case tar.TypeSymlink:
|
||||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
if err := os.MkdirAll(
|
||||||
|
filepath.Dir(target),
|
||||||
|
0755,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,9 +657,10 @@ func extractTarXz(archivePath string, destination string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
linkTarget := header.Linkname
|
if err := os.Symlink(
|
||||||
|
header.Linkname,
|
||||||
if err := os.Symlink(linkTarget, target); err != nil {
|
target,
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Symlink %s konnte nicht erstellt werden: %w",
|
"Symlink %s konnte nicht erstellt werden: %w",
|
||||||
target,
|
target,
|
||||||
@@ -826,21 +679,33 @@ func extractTarXz(archivePath string, destination string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractZip(archivePath string, destination string) error {
|
func extractZip(
|
||||||
|
archivePath string,
|
||||||
|
destination string,
|
||||||
|
) error {
|
||||||
reader, err := zip.OpenReader(archivePath)
|
reader, err := zip.OpenReader(archivePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ZIP-Archiv konnte nicht geöffnet werden: %w", err)
|
return fmt.Errorf(
|
||||||
|
"ZIP-Archiv konnte nicht geöffnet werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
for _, file := range reader.File {
|
for _, file := range reader.File {
|
||||||
target, err := safeJoin(destination, file.Name)
|
target, err := safeJoin(
|
||||||
|
destination,
|
||||||
|
file.Name,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if file.FileInfo().IsDir() {
|
if file.FileInfo().IsDir() {
|
||||||
if err := os.MkdirAll(target, 0755); err != nil {
|
if err := os.MkdirAll(
|
||||||
|
target,
|
||||||
|
0755,
|
||||||
|
); err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Verzeichnis %s konnte nicht erstellt werden: %w",
|
"Verzeichnis %s konnte nicht erstellt werden: %w",
|
||||||
target,
|
target,
|
||||||
@@ -851,7 +716,10 @@ func extractZip(archivePath string, destination string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
if err := os.MkdirAll(
|
||||||
|
filepath.Dir(target),
|
||||||
|
0755,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,10 +769,13 @@ func extractZip(archivePath string, destination string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Helpers
|
// Hilfsfunktionen
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
func safeJoin(base string, name string) (string, error) {
|
func safeJoin(
|
||||||
|
base string,
|
||||||
|
name string,
|
||||||
|
) (string, error) {
|
||||||
if filepath.IsAbs(name) {
|
if filepath.IsAbs(name) {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"unsicherer Archivpfad: %s",
|
"unsicherer Archivpfad: %s",
|
||||||
@@ -915,22 +786,34 @@ func safeJoin(base string, name string) (string, error) {
|
|||||||
cleanName := filepath.Clean(name)
|
cleanName := filepath.Clean(name)
|
||||||
|
|
||||||
if cleanName == ".." ||
|
if cleanName == ".." ||
|
||||||
strings.HasPrefix(cleanName, ".."+string(os.PathSeparator)) {
|
strings.HasPrefix(
|
||||||
|
cleanName,
|
||||||
|
".."+string(os.PathSeparator),
|
||||||
|
) {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"unsicherer Archivpfad: %s",
|
"unsicherer Archivpfad: %s",
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
target := filepath.Join(base, cleanName)
|
target := filepath.Join(
|
||||||
|
base,
|
||||||
|
cleanName,
|
||||||
|
)
|
||||||
|
|
||||||
rel, err := filepath.Rel(base, target)
|
rel, err := filepath.Rel(
|
||||||
|
base,
|
||||||
|
target,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if rel == ".." ||
|
if rel == ".." ||
|
||||||
strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
strings.HasPrefix(
|
||||||
|
rel,
|
||||||
|
".."+string(os.PathSeparator),
|
||||||
|
) {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"unsicherer Archivpfad: %s",
|
"unsicherer Archivpfad: %s",
|
||||||
name,
|
name,
|
||||||
@@ -950,8 +833,13 @@ func removeIfExists(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func envOrDefault(name string, fallback string) string {
|
func envOrDefault(
|
||||||
value := strings.TrimSpace(os.Getenv(name))
|
name string,
|
||||||
|
fallback string,
|
||||||
|
) string {
|
||||||
|
value := strings.TrimSpace(
|
||||||
|
os.Getenv(name),
|
||||||
|
)
|
||||||
|
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fallback
|
return fallback
|
||||||
@@ -970,4 +858,4 @@ func valueOrNone(value string) string {
|
|||||||
|
|
||||||
func printError(err error) {
|
func printError(err error) {
|
||||||
fmt.Printf("\nERROR: %v\n", err)
|
fmt.Printf("\nERROR: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getInstallDir() (string, error) {
|
||||||
|
return "/opt/logofclient", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformRunPrivileged(config Config, action string) error {
|
||||||
|
// Bereits als root gestartet.
|
||||||
|
if os.Geteuid() == 0 {
|
||||||
|
return runAction(config, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Für diese Aktion werden Administratorrechte benötigt.")
|
||||||
|
fmt.Print("Jetzt Administratorrechte anfordern? [J/n]: ")
|
||||||
|
|
||||||
|
var answer string
|
||||||
|
|
||||||
|
if _, err := fmt.Scanln(&answer); err != nil {
|
||||||
|
// Ein leeres Enter kann bei Scanln als Fehler erscheinen.
|
||||||
|
// Das behandeln wir als Zustimmung.
|
||||||
|
answer = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
answer = strings.ToLower(strings.TrimSpace(answer))
|
||||||
|
|
||||||
|
if answer != "" && answer != "j" && answer != "ja" {
|
||||||
|
fmt.Println("Abgebrochen.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Pfad des Installers konnte nicht ermittelt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(
|
||||||
|
"sudo",
|
||||||
|
exe,
|
||||||
|
action,
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Ausführung mit Administratorrechten fehlgeschlagen: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
shell32 = syscall.NewLazyDLL("shell32.dll")
|
||||||
|
shellExecuteExW = shell32.NewProc("ShellExecuteExW")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
seeMaskNocloseprocess = 0x00000040
|
||||||
|
)
|
||||||
|
|
||||||
|
type shellExecuteInfo struct {
|
||||||
|
cbSize uint32
|
||||||
|
fMask uint32
|
||||||
|
hwnd uintptr
|
||||||
|
lpVerb *uint16
|
||||||
|
lpFile *uint16
|
||||||
|
lpParameters *uint16
|
||||||
|
lpDirectory *uint16
|
||||||
|
nShow int32
|
||||||
|
hInstApp uintptr
|
||||||
|
lpIDList uintptr
|
||||||
|
lpClass *uint16
|
||||||
|
hkeyClass uintptr
|
||||||
|
dwHotKey uint32
|
||||||
|
hIcon uintptr
|
||||||
|
hProcess uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInstallDir() (string, error) {
|
||||||
|
programFiles := os.Getenv("ProgramFiles")
|
||||||
|
|
||||||
|
if programFiles == "" {
|
||||||
|
return "", errors.New("ProgramFiles ist nicht gesetzt")
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(programFiles, "logofclient"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformRunPrivileged(config Config, action string) error {
|
||||||
|
if isWindowsAdmin() {
|
||||||
|
return runAction(config, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
return runElevated(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWindowsAdmin() bool {
|
||||||
|
// Ein einfacher und robuster Test:
|
||||||
|
// Versuch, in das Windows-Systemverzeichnis zu schreiben.
|
||||||
|
//
|
||||||
|
// Da wir dort natürlich nichts verändern wollen, verwenden wir
|
||||||
|
// stattdessen "net session". Der Befehl funktioniert nur mit
|
||||||
|
// Administratorrechten.
|
||||||
|
|
||||||
|
cmd := exec.Command("net", "session")
|
||||||
|
cmd.Stdout = nil
|
||||||
|
cmd.Stderr = nil
|
||||||
|
|
||||||
|
return cmd.Run() == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runElevated(action string) error {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Pfad des Installers konnte nicht ermittelt werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
verb, err := syscall.UTF16PtrFromString("runas")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := syscall.UTF16PtrFromString(exe)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters, err := syscall.UTF16PtrFromString(action)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := shellExecuteInfo{
|
||||||
|
cbSize: uint32(unsafe.Sizeof(shellExecuteInfo{})),
|
||||||
|
fMask: seeMaskNocloseprocess,
|
||||||
|
lpVerb: verb,
|
||||||
|
lpFile: file,
|
||||||
|
lpParameters: parameters,
|
||||||
|
nShow: 1, // SW_SHOWNORMAL
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, _, callErr := shellExecuteExW.Call(uintptr(unsafe.Pointer(&info)))
|
||||||
|
|
||||||
|
if ret == 0 {
|
||||||
|
// ERROR_CANCELLED = 1223:
|
||||||
|
// Benutzer hat den UAC-Dialog abgebrochen.
|
||||||
|
if errno, ok := callErr.(syscall.Errno); ok && errno == 1223 {
|
||||||
|
fmt.Println("Administratorrechte wurden nicht erteilt.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Administratorrechte konnten nicht angefordert werden: %w", callErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auf die erhöhte Instanz warten.
|
||||||
|
if info.hProcess != 0 {
|
||||||
|
handle := syscall.Handle(info.hProcess)
|
||||||
|
defer syscall.CloseHandle(handle)
|
||||||
|
|
||||||
|
_, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("auf erhöhte Instanz konnte nicht gewartet werden: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func platformInstallIntegration(installDir string) error {
|
||||||
|
binaryPath := filepath.Join(
|
||||||
|
installDir,
|
||||||
|
"Logof Client",
|
||||||
|
)
|
||||||
|
|
||||||
|
linkPath := "/usr/local/bin/logofclient"
|
||||||
|
|
||||||
|
fmt.Println("Creating command symlink...")
|
||||||
|
|
||||||
|
if err := removeIfExists(linkPath); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"alter Symlink konnte nicht entfernt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Symlink(binaryPath, linkPath); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Symlink konnte nicht erstellt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
desktopDir := "/usr/share/applications"
|
||||||
|
desktopFile := filepath.Join(
|
||||||
|
desktopDir,
|
||||||
|
"logofclient.desktop",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(
|
||||||
|
desktopDir,
|
||||||
|
0755,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Desktop-Verzeichnis konnte nicht erstellt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
iconPath := filepath.Join(
|
||||||
|
installDir,
|
||||||
|
"assets",
|
||||||
|
"icon.png",
|
||||||
|
)
|
||||||
|
|
||||||
|
desktopEntry := fmt.Sprintf(`[Desktop Entry]
|
||||||
|
Name=Logof Client
|
||||||
|
Comment=Logof Client
|
||||||
|
Exec=%s
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Icon=%s
|
||||||
|
Categories=Office;
|
||||||
|
`, linkPath, iconPath)
|
||||||
|
|
||||||
|
if err := os.WriteFile(
|
||||||
|
desktopFile,
|
||||||
|
[]byte(desktopEntry),
|
||||||
|
0644,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Desktop-Datei konnte nicht geschrieben werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformRemoveIntegration() error {
|
||||||
|
linkPath := "/usr/local/bin/logofclient"
|
||||||
|
|
||||||
|
desktopFile := filepath.Join(
|
||||||
|
"/usr/share/applications",
|
||||||
|
"logofclient.desktop",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := removeIfExists(linkPath); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Symlink konnte nicht entfernt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := removeIfExists(desktopFile); err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Desktop-Datei konnte nicht entfernt werden: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/go-ole/go-ole"
|
||||||
|
"github.com/go-ole/go-ole/oleutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func platformInstallIntegration(installDir string) error {
|
||||||
|
targetPath := filepath.Join(installDir, "Logof Client.exe")
|
||||||
|
workingDir := installDir
|
||||||
|
iconPath := filepath.Join(installDir, "assets", "icon.ico")
|
||||||
|
|
||||||
|
startMenuDir := filepath.Join(
|
||||||
|
os.Getenv("ProgramData"),
|
||||||
|
"Microsoft",
|
||||||
|
"Windows",
|
||||||
|
"Start Menu",
|
||||||
|
"Programs",
|
||||||
|
)
|
||||||
|
|
||||||
|
shortcutPath := filepath.Join(
|
||||||
|
startMenuDir,
|
||||||
|
"Logof Client.lnk",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(startMenuDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("Startmenü-Verzeichnis konnte nicht erstellt werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Creating Start Menu shortcut...")
|
||||||
|
|
||||||
|
if err := platformCreateShortcut(
|
||||||
|
shortcutPath,
|
||||||
|
targetPath,
|
||||||
|
workingDir,
|
||||||
|
iconPath,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformRemoveIntegration() error {
|
||||||
|
startMenuDir := filepath.Join(
|
||||||
|
os.Getenv("ProgramData"),
|
||||||
|
"Microsoft",
|
||||||
|
"Windows",
|
||||||
|
"Start Menu",
|
||||||
|
"Programs",
|
||||||
|
)
|
||||||
|
|
||||||
|
shortcutPath := filepath.Join(
|
||||||
|
startMenuDir,
|
||||||
|
"Logof Client.lnk",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := os.Remove(shortcutPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("Startmenü-Verknüpfung konnte nicht entfernt werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func platformCreateShortcut(
|
||||||
|
shortcutPath string,
|
||||||
|
targetPath string,
|
||||||
|
workingDir string,
|
||||||
|
iconPath string,
|
||||||
|
) error {
|
||||||
|
// COM für den aktuellen Thread initialisieren.
|
||||||
|
if err := ole.CoInitialize(0); err != nil {
|
||||||
|
return fmt.Errorf("COM konnte nicht initialisiert werden: %w", err)
|
||||||
|
}
|
||||||
|
defer ole.CoUninitialize()
|
||||||
|
|
||||||
|
// Windows Scripting Host.
|
||||||
|
unknown, err := oleutil.CreateObject("WScript.Shell")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("WScript.Shell konnte nicht erstellt werden: %w", err)
|
||||||
|
}
|
||||||
|
defer unknown.Release()
|
||||||
|
|
||||||
|
shell, err := unknown.QueryInterface(ole.IID_IDispatch)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("WScript.Shell IDispatch konnte nicht abgerufen werden: %w", err)
|
||||||
|
}
|
||||||
|
defer shell.Release()
|
||||||
|
|
||||||
|
// WScript.Shell.CreateShortcut(...)
|
||||||
|
result, err := oleutil.CallMethod(shell, "CreateShortcut", shortcutPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Shortcut-Objekt konnte nicht erstellt werden: %w", err)
|
||||||
|
}
|
||||||
|
defer result.Clear()
|
||||||
|
|
||||||
|
shortcut := result.ToIDispatch()
|
||||||
|
if shortcut == nil {
|
||||||
|
return fmt.Errorf("ungültiges Shortcut-Objekt erhalten")
|
||||||
|
}
|
||||||
|
defer shortcut.Release()
|
||||||
|
|
||||||
|
// Zielprogramm.
|
||||||
|
if _, err := oleutil.PutProperty(shortcut, "TargetPath", targetPath); err != nil {
|
||||||
|
return fmt.Errorf("TargetPath konnte nicht gesetzt werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arbeitsverzeichnis.
|
||||||
|
if _, err := oleutil.PutProperty(shortcut, "WorkingDirectory", workingDir); err != nil {
|
||||||
|
return fmt.Errorf("WorkingDirectory konnte nicht gesetzt werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beschreibung.
|
||||||
|
if _, err := oleutil.PutProperty(shortcut, "Description", "Logof Client"); err != nil {
|
||||||
|
return fmt.Errorf("Description konnte nicht gesetzt werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon.
|
||||||
|
if _, err := oleutil.PutProperty(shortcut, "IconLocation", iconPath); err != nil {
|
||||||
|
return fmt.Errorf("IconLocation konnte nicht gesetzt werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verknüpfung tatsächlich auf die Festplatte schreiben.
|
||||||
|
if _, err := oleutil.CallMethod(shortcut, "Save"); err != nil {
|
||||||
|
return fmt.Errorf("Shortcut konnte nicht gespeichert werden: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user