Eigener Installer mit Standard- und Portable-Modus, Updater fuer beide Varianten

This commit is contained in:
Davito committed 2026-10-14 10:34:48 +02:00
1 parent df88f8abaa
commit 92d02d0008
6 files changed
+508 -36

No files matched your search

+7 -4
View File
@@ -38,14 +38,17 @@ Ein moderner, performanter und schlanker Desktop-Client für Windows 64-bit, geb
## Schnellstart
1. **Installieren:** `EuerChat_<Version>_x64-setup.exe` ausführen (Installation pro Benutzer, keine Administratorrechte nötig). Beim ersten Start einfach den Namen eingeben und verbinden.
* Die Konfiguration liegt in `%APPDATA%\EuerChat\config.json` und enthält eventuell Passwörter. Sie ist nicht Teil des Installers und gehört nicht ins Repository (steht in `.gitignore`).
* Alternativ ohne Installation: `EuerChat.exe` direkt starten. Eine alte `config.json` neben der EXE wird noch gelesen und beim nächsten Speichern nach `%APPDATA%` übernommen.
1. **Installieren:** `EuerChat_<Version>_x64-setup.exe` ausführen und die Installationsart wählen. Beim ersten Start einfach den Namen eingeben und verbinden.
* **Standard** (empfohlen): für alle Benutzer, standardmäßig nach `C:\Program Files\EuerChat`, mit Startmenü- und Desktop-Verknüpfung. Erfordert Administratorrechte (UAC-Abfrage), Deinstallation über die Windows-Einstellungen. Die Konfiguration liegt in `%APPDATA%\EuerChat\config.json`.
* **Portable:** nur Kopieren in einen frei wählbaren Ordner (Vorschlag: neben dem Installer), z. B. auf einen USB-Stick. Keine Administratorrechte, keine Registry-Einträge. Die Konfiguration liegt im Unterordner `data` (Marker-Datei `portable.flag`). Entfernen: Ordner löschen.
* Die Konfiguration kann Passwörter enthalten. Sie ist nicht Teil des Installers und gehört nicht ins Repository (steht in `.gitignore`).
* Der Installer lässt sich auch still nutzen: `/S /MODE=standard|portable /D=<Ordner>` (`/D=` immer zuletzt). Der Updater verwendet genau diese Parameter und aktualisiert Standard- und Portable-Installationen an Ort und Stelle (Standard mit UAC-Abfrage, Portable ohne). Einstellungen bleiben erhalten.
* Alternativ ohne Installer: `EuerChat.exe` direkt starten. Eine alte `config.json` neben der EXE wird noch gelesen und beim nächsten Speichern nach `%APPDATA%` übernommen.
2. **Installer bauen:**
```powershell
.\build.bat
```
Benötigt Rust und Node.js. Der Installer liegt danach in `dist\`, die portable `EuerChat.exe` im Hauptordner.
Benötigt Rust, Node.js und NSIS 3 (`winget install NSIS.NSIS`; der von Tauri geladene NSIS-Cache wird ebenfalls gefunden). Das Skript baut die App ohne Tauri-Bundler und dann den eigenen Installer `installer\EuerChat.nsi`. Ergebnis: `dist\EuerChat_<Version>_x64-setup.exe`, dazu die lose `EuerChat.exe` im Hauptordner.
3. **Tests:** `cd src-tauri; cargo test`
4. **Release veröffentlichen:**
1. Version in `src-tauri\tauri.conf.json` und `src-tauri\Cargo.toml` erhöhen, optional Release-Notizen in `RELEASE_NOTES.md` schreiben.
+16 -4
View File
@@ -8,14 +8,26 @@ if not exist node_modules (
if errorlevel 1 goto :fail
)
echo Baue Installer (NSIS)...
call npx tauri build
echo Baue EuerChat.exe ...
call npx tauri build --no-bundle
if errorlevel 1 goto :fail
for /f "usebackq delims=" %%v in (`node -p "require('./src-tauri/tauri.conf.json').version"`) do set VERSION=%%v
set MAKENSIS=%LOCALAPPDATA%\tauri\NSIS\makensis.exe
if not exist "%MAKENSIS%" set MAKENSIS=%ProgramFiles(x86)%\NSIS\makensis.exe
if not exist "%MAKENSIS%" (
echo [FEHLER] makensis nicht gefunden. NSIS 3 installieren: winget install NSIS.NSIS
goto :fail
)
if not exist dist mkdir dist
copy /y "src-tauri\target\release\bundle\nsis\EuerChat_*_x64-setup.exe" dist\ >nul
echo Baue Installer %VERSION% ...
"%MAKENSIS%" /V2 /DVERSION=%VERSION% installer\EuerChat.nsi
if errorlevel 1 goto :fail
copy /y src-tauri\target\release\euer-chat.exe EuerChat.exe >nul
echo [OK] Installer liegt in dist\ - EuerChat.exe (portabel) im Hauptordner.
echo [OK] Installer: dist\EuerChat_%VERSION%_x64-setup.exe
pause
exit /b 0
+387
View File
@@ -0,0 +1,387 @@
; EuerChat-Installer
; Standard (Program Files, alle Benutzer) oder Portable (frei waehlbarer Ordner).
; Parameter, auch fuer stille Updates: /S /UPDATE /MODE=standard|portable /NS /D=<Ordner> (/D= immer zuletzt)
Unicode true
ManifestDPIAware true
SetCompressor /SOLID lzma
RequestExecutionLevel user
!ifndef VERSION
!define VERSION "0.0.0"
!endif
!ifndef EXE_SRC
!define EXE_SRC "..\src-tauri\target\release\euer-chat.exe"
!endif
!ifndef OUT_FILE
!define OUT_FILE "..\dist\EuerChat_${VERSION}_x64-setup.exe"
!endif
!define PRODUCT "EuerChat"
!define PUBLISHER "euer.tv"
!define MAINEXE "euer-chat.exe"
!define BUNDLEID "tv.euer.chat"
!define WEBVIEW2APPGUID "{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
!define PLACEHOLDER_DIR "placeholder\${PRODUCT}"
; TEST_USER_CONTEXT: Testbuild ohne Administratorrechte (HKCU, eigener Registry-Schluessel).
!ifdef TEST_USER_CONTEXT
!define ROOT HKCU
!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}-Test"
!else
!define ROOT HKLM
!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT}"
!endif
Name "${PRODUCT}"
OutFile "${OUT_FILE}"
BrandingText "${PRODUCT} ${VERSION}"
InstallDir "${PLACEHOLDER_DIR}"
VIProductVersion "${VERSION}.0"
VIAddVersionKey "ProductName" "${PRODUCT}"
VIAddVersionKey "FileDescription" "${PRODUCT} Installer"
VIAddVersionKey "FileVersion" "${VERSION}"
VIAddVersionKey "ProductVersion" "${VERSION}"
VIAddVersionKey "CompanyName" "${PUBLISHER}"
VIAddVersionKey "LegalCopyright" "${PUBLISHER}"
!include MUI2.nsh
!include FileFunc.nsh
!include LogicLib.nsh
!include nsDialogs.nsh
!include x64.nsh
!insertmacro GetParameters
!insertmacro GetOptions
!insertmacro un.GetParameters
!insertmacro un.GetOptions
Var Portable
Var UpdateMode
Var NoShortcut
Var Elevated
Var ModeGiven
Var RadioStandard
Var RadioPortable
!define MUI_ICON "..\src-tauri\icons\icon.ico"
!define MUI_UNICON "..\src-tauri\icons\icon.ico"
!define MUI_ABORTWARNING
; Seiten
!define MUI_PAGE_CUSTOMFUNCTION_PRE WelcomePre
!insertmacro MUI_PAGE_WELCOME
Page custom ModePageCreate ModePageLeave
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_NOAUTOCLOSE
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "${PRODUCT} jetzt starten"
!define MUI_FINISHPAGE_RUN_FUNCTION RunMainBinary
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "German"
; ---------------------------------------------------------------------------
; Hilfsfunktionen
; ---------------------------------------------------------------------------
Function SetDefaultDir
${If} $Portable = 1
StrCpy $INSTDIR "$EXEDIR\${PRODUCT}"
${Else}
ReadRegStr $0 ${ROOT} "${UNINSTKEY}" "InstallLocation"
${If} $0 != ""
StrCpy $INSTDIR $0
${Else}
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCT}"
${EndIf}
${EndIf}
FunctionEnd
; Startet den Installer bei Bedarf mit Administratorrechten neu (nur fuer die Standard-Installation).
Function RequireAdmin
!ifndef TEST_USER_CONTEXT
System::Call 'shell32::IsUserAnAdmin()i.r0'
${If} $0 = 0
${If} $Elevated = 1
${IfNot} ${Silent}
MessageBox MB_ICONSTOP "Für die Standard-Installation sind Administratorrechte nötig."
${EndIf}
Abort
${EndIf}
StrCpy $1 "/MODE=standard /ELEVATED"
${If} ${Silent}
StrCpy $1 "$1 /S"
${EndIf}
${If} $UpdateMode = 1
StrCpy $1 "$1 /UPDATE"
${EndIf}
${If} $NoShortcut = 1
StrCpy $1 "$1 /NS"
${EndIf}
StrCpy $1 "$1 /D=$INSTDIR"
System::Call 'shell32::ShellExecute(i 0, t "runas", t "$EXEPATH", t "$1", t "", i 1) i .r2'
${If} $2 > 32
Quit
${EndIf}
${IfNot} ${Silent}
MessageBox MB_ICONEXCLAMATION "Ohne Administratorrechte kann die Standard-Installation nicht fortgesetzt werden. Wähle stattdessen „Portable“."
${EndIf}
Abort
${EndIf}
!endif
FunctionEnd
Function CloseRunningApp
nsExec::ExecToStack 'cmd /c tasklist /FI "IMAGENAME eq ${MAINEXE}" /NH | find /I "${MAINEXE}"'
Pop $0
Pop $1
${If} $0 = 0
${IfNot} ${Silent}
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "${PRODUCT} läuft noch und wird für die Installation beendet." IDOK +2
Abort
${EndIf}
nsExec::ExecToStack 'taskkill /F /IM "${MAINEXE}"'
Pop $0
Pop $1
Sleep 800
${EndIf}
FunctionEnd
Function RunMainBinary
; Ueber den Explorer starten, damit die App ohne Administratorrechte laeuft.
Exec '"$WINDIR\explorer.exe" "$INSTDIR\${MAINEXE}"'
FunctionEnd
; ---------------------------------------------------------------------------
; Installer
; ---------------------------------------------------------------------------
Function .onInit
${If} ${RunningX64}
SetRegView 64
${EndIf}
${GetParameters} $R9
${GetOptions} $R9 "/UPDATE" $0
${IfNot} ${Errors}
StrCpy $UpdateMode 1
${EndIf}
${GetOptions} $R9 "/NS" $0
${IfNot} ${Errors}
StrCpy $NoShortcut 1
${EndIf}
${GetOptions} $R9 "/ELEVATED" $0
${IfNot} ${Errors}
StrCpy $Elevated 1
${EndIf}
${GetOptions} $R9 "/MODE=" $0
${IfNot} ${Errors}
StrCpy $ModeGiven 1
${If} $0 == "portable"
StrCpy $Portable 1
${EndIf}
${EndIf}
; Stille Installation ohne Angabe: Standard
${If} ${Silent}
StrCpy $ModeGiven 1
${EndIf}
${If} $INSTDIR == "${PLACEHOLDER_DIR}"
Call SetDefaultDir
${EndIf}
${If} $ModeGiven = 1
${AndIf} $Portable <> 1
Call RequireAdmin
${EndIf}
FunctionEnd
Function WelcomePre
${If} $Elevated = 1
Abort
${EndIf}
FunctionEnd
Function ModePageCreate
${If} $ModeGiven = 1
Abort
${EndIf}
!insertmacro MUI_HEADER_TEXT "Installationsart" "Wie soll ${PRODUCT} installiert werden?"
nsDialogs::Create 1018
Pop $0
${NSD_CreateRadioButton} 0 6u 100% 12u "Standard-Installation (empfohlen)"
Pop $RadioStandard
${NSD_AddStyle} $RadioStandard ${WS_GROUP}
${NSD_CreateLabel} 12u 20u 94% 38u "Installiert ${PRODUCT} für alle Benutzer, standardmäßig nach C:\Program Files. Mit Startmenü- und Desktop-Verknüpfung; deinstallierbar über die Windows-Einstellungen. Erfordert Administratorrechte."
Pop $0
${NSD_CreateRadioButton} 0 66u 100% 12u "Portable (Ordner frei wählbar)"
Pop $RadioPortable
${NSD_CreateLabel} 12u 80u 94% 46u "Kopiert ${PRODUCT} nur in einen Ordner deiner Wahl, z. B. auf einen USB-Stick. Keine Administratorrechte und keine Einträge in der Registry. Die Einstellungen liegen im Unterordner „data“. Zum Entfernen genügt es, den Ordner zu löschen."
Pop $0
${If} $Portable = 1
${NSD_Check} $RadioPortable
${Else}
${NSD_Check} $RadioStandard
${EndIf}
nsDialogs::Show
FunctionEnd
Function ModePageLeave
${NSD_GetState} $RadioPortable $0
${If} $0 = ${BST_CHECKED}
StrCpy $Portable 1
${Else}
StrCpy $Portable 0
${EndIf}
Call SetDefaultDir
${If} $Portable = 0
Call RequireAdmin
${EndIf}
FunctionEnd
Section "WebView2"
${If} $UpdateMode <> 1
ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${If} $4 == ""
ReadRegStr $4 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${EndIf}
${If} $4 == ""
DetailPrint "Microsoft Edge WebView2 wird heruntergeladen ..."
Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
NSISdl::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
Pop $0
${If} $0 != "success"
Abort "WebView2 konnte nicht heruntergeladen werden. Bitte die Internetverbindung prüfen und erneut versuchen."
${EndIf}
DetailPrint "Microsoft Edge WebView2 wird installiert ..."
ExecWait '"$TEMP\MicrosoftEdgeWebview2Setup.exe" /silent /install' $1
${If} $1 <> 0
Abort "Die Installation von WebView2 ist fehlgeschlagen."
${EndIf}
${EndIf}
${EndIf}
SectionEnd
Section "Install"
SetOutPath $INSTDIR
Call CloseRunningApp
File "${EXE_SRC}"
${If} $Portable = 1
; Die Marker-Datei schaltet den portablen Datenordner der App frei.
FileOpen $0 "$INSTDIR\portable.flag" w
FileWrite $0 "${PRODUCT} portable: Einstellungen liegen im Ordner data.$\r$\n"
FileClose $0
CreateDirectory "$INSTDIR\data"
${Else}
Delete "$INSTDIR\portable.flag"
WriteUninstaller "$INSTDIR\uninstall.exe"
!ifdef TEST_USER_CONTEXT
SetShellVarContext current
!else
SetShellVarContext all
!endif
WriteRegStr ${ROOT} "${UNINSTKEY}" "DisplayName" "${PRODUCT}"
WriteRegStr ${ROOT} "${UNINSTKEY}" "DisplayVersion" "${VERSION}"
WriteRegStr ${ROOT} "${UNINSTKEY}" "Publisher" "${PUBLISHER}"
WriteRegStr ${ROOT} "${UNINSTKEY}" "InstallLocation" "$INSTDIR"
WriteRegStr ${ROOT} "${UNINSTKEY}" "DisplayIcon" "$INSTDIR\${MAINEXE}"
WriteRegStr ${ROOT} "${UNINSTKEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegDWORD ${ROOT} "${UNINSTKEY}" "NoModify" 1
WriteRegDWORD ${ROOT} "${UNINSTKEY}" "NoRepair" 1
WriteRegDWORD ${ROOT} "${UNINSTKEY}" "EstimatedSize" 7600
CreateShortcut "$SMPROGRAMS\${PRODUCT}.lnk" "$INSTDIR\${MAINEXE}"
${If} $UpdateMode <> 1
${AndIf} $NoShortcut <> 1
CreateShortcut "$DESKTOP\${PRODUCT}.lnk" "$INSTDIR\${MAINEXE}"
${EndIf}
${EndIf}
SectionEnd
; ---------------------------------------------------------------------------
; Deinstallation (nur Standard-Installation)
; ---------------------------------------------------------------------------
Function un.onInit
${If} ${RunningX64}
SetRegView 64
${EndIf}
${GetParameters} $R9
${GetOptions} $R9 "/ELEVATED" $0
${IfNot} ${Errors}
StrCpy $Elevated 1
${EndIf}
${GetOptions} $R9 "/UPDATE" $0
${IfNot} ${Errors}
StrCpy $UpdateMode 1
${EndIf}
!ifndef TEST_USER_CONTEXT
System::Call 'shell32::IsUserAnAdmin()i.r0'
${If} $0 = 0
${If} $Elevated = 1
Abort
${EndIf}
StrCpy $1 "/ELEVATED"
${If} ${Silent}
StrCpy $1 "$1 /S"
${EndIf}
${If} $UpdateMode = 1
StrCpy $1 "$1 /UPDATE"
${EndIf}
StrCpy $1 "$1 _?=$INSTDIR"
System::Call 'shell32::ShellExecute(i 0, t "runas", t "$EXEPATH", t "$1", t "", i 1) i .r2'
${If} $2 > 32
Quit
${EndIf}
Abort
${EndIf}
!endif
FunctionEnd
Section "Uninstall"
!ifdef TEST_USER_CONTEXT
SetShellVarContext current
!else
SetShellVarContext all
!endif
nsExec::ExecToStack 'taskkill /F /IM "${MAINEXE}"'
Pop $0
Pop $1
Sleep 500
Delete "$INSTDIR\${MAINEXE}"
Delete "$INSTDIR\uninstall.exe"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\${PRODUCT}.lnk"
Delete "$DESKTOP\${PRODUCT}.lnk"
DeleteRegKey ${ROOT} "${UNINSTKEY}"
${If} $UpdateMode <> 1
${AndIfNot} ${Silent}
MessageBox MB_YESNO|MB_ICONQUESTION|MB_DEFBUTTON2 "Sollen auch die Einstellungen von ${PRODUCT} (Konfiguration und gespeicherte Passwörter) gelöscht werden?" IDNO skip_data
SetShellVarContext current
RMDir /r "$APPDATA\${PRODUCT}"
RMDir /r "$LOCALAPPDATA\${BUNDLEID}"
skip_data:
${EndIf}
SectionEnd
+9 -1
View File
@@ -5,14 +5,22 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use tauri::{PhysicalPosition, PhysicalSize};
fn exe_dir() -> PathBuf {
pub fn exe_dir() -> PathBuf {
std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(PathBuf::from))
.unwrap_or_else(|| PathBuf::from("."))
}
/// Der Installer legt bei der portablen Installation die Marker-Datei `portable.flag` neben die EXE.
pub fn is_portable() -> bool {
exe_dir().join("portable.flag").is_file()
}
pub fn data_dir() -> PathBuf {
if is_portable() {
return exe_dir().join("data");
}
std::env::var_os("APPDATA")
.map(|d| PathBuf::from(d).join("EuerChat"))
.unwrap_or_else(exe_dir)
+87 -16
View File
@@ -366,28 +366,65 @@ async fn download_to_file(
// Installation
// ---------------------------------------------------------------------------
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum InstallKind {
/// Mit dem Standard-Installer installiert (uninstall.exe neben der EXE), meist in Program Files.
Standard,
/// Portable Installation (portable.flag neben der EXE).
Portable,
/// Lose EXE ohne Installation, z. B. aus dem Build-Ordner.
Loose,
}
fn detect_install_kind(dir: &Path) -> InstallKind {
if dir.join("portable.flag").is_file() {
InstallKind::Portable
} else if dir.join("uninstall.exe").is_file() {
InstallKind::Standard
} else {
InstallKind::Loose
}
}
/// PowerShell-Skript: wartet auf das Ende dieser App, installiert still und startet EuerChat neu.
fn build_update_script(pid: u32, installer: &Path, current_exe: &Path, kind: InstallKind) -> String {
let quote = |p: &Path| p.to_string_lossy().replace('\'', "''");
let dir = current_exe.parent().map(quote).unwrap_or_default();
// Standard-Installationen liegen meist in Program Files und brauchen Administratorrechte (UAC).
let (args, elevate) = match kind {
InstallKind::Standard => (format!("/S /UPDATE /MODE=standard /D={dir}"), true),
InstallKind::Portable => (format!("/S /UPDATE /MODE=portable /D={dir}"), false),
InstallKind::Loose => ("/S /UPDATE /MODE=standard".to_string(), true),
};
let verb = if elevate { " -Verb RunAs" } else { "" };
let relaunch = match kind {
InstallKind::Loose => format!(
"$t = Join-Path $env:ProgramFiles 'EuerChat\\euer-chat.exe'; \
if (Test-Path $t) {{ Start-Process -FilePath $t }} else {{ Start-Process -FilePath '{}' }}",
quote(current_exe)
),
_ => format!("Start-Process -FilePath '{}'", quote(current_exe)),
};
// Bricht der Nutzer die UAC-Abfrage ab, wird trotzdem die bisherige Version neu gestartet.
format!(
"Wait-Process -Id {pid} -Timeout 30 -ErrorAction SilentlyContinue; \
try {{ Start-Process -FilePath '{installer}' -ArgumentList '{args}'{verb} -Wait }} catch {{}}; \
{relaunch}",
installer = quote(installer),
)
}
/// Startet den Installer erst, wenn diese App beendet ist, und danach EuerChat neu.
fn spawn_installer_after_exit(installer: &Path) -> std::io::Result<()> {
use std::os::windows::process::CommandExt;
let quote = |p: &Path| p.to_string_lossy().replace('\'', "''");
let current = std::env::current_exe()?;
let installed_here = current
let kind = current
.parent()
.is_some_and(|dir| dir.join("uninstall.exe").exists());
// Eine portable EXE wird per Installer in %LOCALAPPDATA%\EuerChat aktualisiert; dort neu starten.
let script = format!(
"Wait-Process -Id {pid} -Timeout 30 -ErrorAction SilentlyContinue; \
Start-Process -FilePath '{installer}' -ArgumentList '/S','/UPDATE' -Wait; \
$installed = Join-Path $env:LOCALAPPDATA 'EuerChat\\euer-chat.exe'; \
if (-not {installed_here} -and (Test-Path $installed)) {{ Start-Process -FilePath $installed }} \
else {{ Start-Process -FilePath '{current}' }}",
pid = std::process::id(),
installer = quote(installer),
installed_here = if installed_here { "$true" } else { "$false" },
current = quote(&current),
);
.map_or(InstallKind::Loose, detect_install_kind);
let script = build_update_script(std::process::id(), installer, &current, kind);
std::process::Command::new("powershell.exe")
.args(["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", &script])
@@ -529,6 +566,40 @@ mod tests {
assert!(parse_expected_checksum(&other, "x.exe").unwrap_err().contains("x.exe"));
}
#[test]
fn install_kind_detection() {
let dir = std::env::temp_dir().join(format!("euerchat-kind-test-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
assert_eq!(detect_install_kind(&dir), InstallKind::Loose);
std::fs::write(dir.join("uninstall.exe"), b"x").unwrap();
assert_eq!(detect_install_kind(&dir), InstallKind::Standard);
std::fs::write(dir.join("portable.flag"), b"x").unwrap();
assert_eq!(detect_install_kind(&dir), InstallKind::Portable);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn update_script_per_install_kind() {
let installer = Path::new(r"C:\Users\O'Brien\AppData\Roaming\EuerChat\updates\EuerChat_0.2.0_x64-setup.exe");
let exe = Path::new(r"C:\Program Files\EuerChat\euer-chat.exe");
let standard = build_update_script(42, installer, exe, InstallKind::Standard);
assert!(standard.contains("Wait-Process -Id 42"));
assert!(standard.contains("/MODE=standard /D=C:\\Program Files\\EuerChat'"));
assert!(standard.contains("-Verb RunAs"));
assert!(standard.contains("O''Brien"), "Apostrophe müssen verdoppelt werden");
assert!(standard.contains("Start-Process -FilePath 'C:\\Program Files\\EuerChat\\euer-chat.exe'"));
let portable = build_update_script(1, installer, Path::new(r"E:\Tools\EuerChat\euer-chat.exe"), InstallKind::Portable);
assert!(portable.contains("/MODE=portable /D=E:\\Tools\\EuerChat'"));
assert!(!portable.contains("RunAs"), "portable Updates brauchen keine Administratorrechte");
let loose = build_update_script(1, installer, Path::new(r"D:\build\euer-chat.exe"), InstallKind::Loose);
assert!(loose.contains("/MODE=standard'"));
assert!(!loose.contains("/D="));
assert!(loose.contains("$env:ProgramFiles"));
}
#[test]
fn managed_installer_files() {
assert!(is_managed_installer_file("EuerChat_0.2.0_x64-setup.exe"));
+2 -11
View File
@@ -39,16 +39,7 @@
}
},
"bundle": {
"active": true,
"targets": ["nsis"],
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.ico"],
"shortDescription": "EuerChat - IRC-Chat und Livestream-Client fuer euer.tv",
"windows": {
"nsis": {
"installMode": "currentUser",
"languages": ["German"],
"displayLanguageSelector": false
}
}
"active": false,
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.ico"]
}
}