Skip to content

Commit ad17f2e

Browse files
committed
fixed installers
1 parent ab0ea0f commit ad17f2e

File tree

6 files changed

+144
-60
lines changed

6 files changed

+144
-60
lines changed

.github/workflows/build-macos-arm-installer.yml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ jobs:
8888
8989
- name: Build macOS ARM executable
9090
run: |
91+
# Clean any existing output directory
92+
rm -rf dist/Cleanuparr.app/Contents/MacOS
93+
mkdir -p dist/Cleanuparr.app/Contents/MacOS
94+
9195
dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj \
9296
-c Release \
9397
--runtime osx-arm64 \
@@ -96,7 +100,10 @@ jobs:
96100
/p:PublishSingleFile=true \
97101
/p:Version=${{ env.appVersion }} \
98102
/p:DebugType=None \
99-
/p:DebugSymbols=false
103+
/p:DebugSymbols=false \
104+
/p:EnableCompressionInSingleFile=false \
105+
/p:IncludeNativeLibrariesForSelfExtract=true \
106+
/p:UseAppHost=true
100107
101108
- name: Verify architecture
102109
run: |
@@ -169,15 +176,6 @@ jobs:
169176
</plist>
170177
EOF
171178
172-
# Create sample configuration with simplified settings
173-
mkdir -p dist/Cleanuparr.app/Contents/Resources
174-
cat > dist/Cleanuparr.app/Contents/Resources/config/cleanuparr.json << EOF
175-
{
176-
"HTTP_PORTS": 11011,
177-
"BASE_PATH": "/"
178-
}
179-
EOF
180-
181179
- name: Create PKG installer
182180
run: |
183181
# Create postinstall script

.github/workflows/build-macos-intel-installer.yml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ jobs:
8888
8989
- name: Build macOS Intel executable
9090
run: |
91+
# Clean any existing output directory
92+
rm -rf dist/Cleanuparr.app/Contents/MacOS
93+
mkdir -p dist/Cleanuparr.app/Contents/MacOS
94+
9195
dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj \
9296
-c Release \
9397
--runtime osx-x64 \
@@ -96,7 +100,10 @@ jobs:
96100
/p:PublishSingleFile=true \
97101
/p:Version=${{ env.appVersion }} \
98102
/p:DebugType=None \
99-
/p:DebugSymbols=false
103+
/p:DebugSymbols=false \
104+
/p:EnableCompressionInSingleFile=false \
105+
/p:IncludeNativeLibrariesForSelfExtract=true \
106+
/p:UseAppHost=true
100107
101108
- name: Verify architecture
102109
run: |
@@ -168,15 +175,6 @@ jobs:
168175
</dict>
169176
</plist>
170177
EOF
171-
172-
# Create sample configuration with simplified settings
173-
mkdir -p dist/Cleanuparr.app/Contents/Resources/config
174-
cat > dist/Cleanuparr.app/Contents/Resources/config/cleanuparr.json << EOF
175-
{
176-
"HTTP_PORTS": 11011,
177-
"BASE_PATH": "/"
178-
}
179-
EOF
180178
181179
- name: Create PKG installer
182180
run: |

code/backend/Cleanuparr.Shared/Helpers/BasePathValidator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ public static ValidationResult Validate(string? basePath)
2525

2626
// Trim whitespace
2727
basePath = basePath.Trim();
28-
28+
29+
// Check for just root path
2930
if (basePath == "/")
3031
{
31-
return ValidationResult.Success();
32+
return ValidationResult.Failure("BASE_PATH cannot be just '/' (conflicts with root)");
3233
}
3334

3435
// Check length

code/frontend/angular.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": 1,
44
"newProjectRoot": "projects",
55
"projects": {
6-
"UI": {
6+
"Cleanuparr": {
77
"projectType": "application",
88
"schematics": {
99
"@schematics/angular:component": {

code/frontend/public/manifest.webmanifest

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "UI",
3-
"short_name": "UI",
2+
"name": "Cleanuparr",
3+
"short_name": "Cleanuparr",
44
"theme_color": "#1976d2",
55
"background_color": "#fafafa",
66
"display": "standalone",

installers/windows/cleanuparr-installer.iss

Lines changed: 122 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
#define MyServiceName "Cleanuparr"
77

88
[Setup]
9-
; NOTE: The value of AppId uniquely identifies this application.
10-
; Do not use the same AppId value in installers for other applications.
119
AppId={{E8B2C9D4-6F87-4E42-B5C3-29E121D4BDFF}
1210
AppName={#MyAppName}
1311
AppVersion={#MyAppVersion}
@@ -43,10 +41,8 @@ Name: "installservice"; Description: "Install as Windows Service (Recommended)";
4341

4442
[Files]
4543
Source: "dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
46-
; Application icon
4744
Source: "Logo\favicon.ico"; DestDir: "{app}"; Flags: ignoreversion
48-
; Create sample configuration
49-
Source: "config\cleanuparr.json"; DestDir: "{app}\config"; Flags: ignoreversion; AfterInstall: CreateConfigDirs
45+
; Note: Application will create its own configuration files
5046

5147
[Dirs]
5248
Name: "{app}\config"; Permissions: everyone-full
@@ -57,70 +53,161 @@ Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
5753
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\favicon.ico"; Tasks: desktopicon
5854

5955
[Run]
60-
; Stop any existing service first
61-
Filename: "{sys}\sc.exe"; Parameters: "stop ""{#MyServiceName}"""; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
62-
; Wait for service to stop
63-
Filename: "{sys}\timeout.exe"; Parameters: "/t 3"; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
64-
; Remove existing service
65-
Filename: "{sys}\sc.exe"; Parameters: "delete ""{#MyServiceName}"""; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
66-
; Install as service
67-
Filename: "{sys}\sc.exe"; Parameters: "create ""{#MyServiceName}"" binPath= ""\""{app}\{#MyAppExeName}\"""" DisplayName= ""{#MyAppName}"" start= auto"; Tasks: installservice; Flags: runhidden
68-
; Configure service description
69-
Filename: "{sys}\sc.exe"; Parameters: "description ""{#MyServiceName}"" ""Cleanuparr download management service"""; Tasks: installservice; Flags: runhidden
70-
; Start the service
71-
Filename: "{sys}\sc.exe"; Parameters: "start ""{#MyServiceName}"""; Tasks: installservice; Flags: runhidden
72-
; Open web interface
73-
Filename: "http://localhost:11011"; Description: "Open Cleanuparr Web Interface"; Flags: postinstall shellexec nowait
56+
; For fresh installs only - create and start service
57+
Filename: "{sys}\sc.exe"; Parameters: "create ""{#MyServiceName}"" binPath= ""\""{app}\{#MyAppExeName}\"""" DisplayName= ""{#MyAppName}"" start= auto"; Tasks: installservice; Flags: runhidden; Check: not ServiceExists('{#MyServiceName}')
58+
Filename: "{sys}\sc.exe"; Parameters: "description ""{#MyServiceName}"" ""Cleanuparr download management service"""; Tasks: installservice; Flags: runhidden; Check: not ServiceExists('{#MyServiceName}')
59+
60+
; For updates - stop service if running, wait for complete shutdown, then restart
61+
Filename: "{sys}\sc.exe"; Parameters: "stop ""{#MyServiceName}"""; Flags: runhidden; Check: ServiceExists('{#MyServiceName}') and IsServiceRunning('{#MyServiceName}') and IsTaskSelected('installservice')
62+
Filename: "{sys}\sc.exe"; Parameters: "start ""{#MyServiceName}"""; Tasks: installservice; Flags: runhidden; Check: ServiceExists('{#MyServiceName}') and WasServiceExisting
63+
64+
; For fresh installs - start the newly created service
65+
Filename: "{sys}\sc.exe"; Parameters: "start ""{#MyServiceName}"""; Tasks: installservice; Flags: runhidden; Check: not WasServiceExisting
66+
67+
; Open web interface (only if service is selected)
68+
Filename: "http://localhost:11011"; Description: "Open Cleanuparr Web Interface"; Flags: postinstall shellexec nowait; Check: IsTaskSelected('installservice')
69+
7470
; Run directly (if not installed as service)
7571
Filename: "{app}\{#MyAppExeName}"; Description: "Run {#MyAppName} Application"; Flags: nowait postinstall skipifsilent; Check: not IsTaskSelected('installservice')
7672

7773
[UninstallRun]
78-
; Stop the service first
7974
Filename: "{sys}\sc.exe"; Parameters: "stop ""{#MyServiceName}"""; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
80-
; Wait for service to stop
81-
Filename: "{sys}\timeout.exe"; Parameters: "/t 3"; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
82-
; Remove the service
75+
Filename: "{sys}\timeout.exe"; Parameters: "/t 5"; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
8376
Filename: "{sys}\sc.exe"; Parameters: "delete ""{#MyServiceName}"""; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
8477

8578
[Code]
79+
var
80+
WasServiceExisting: Boolean;
81+
8682
procedure CreateConfigDirs;
8783
begin
88-
// Create necessary directories with proper permissions
84+
// Create config directory - application will create its own config files
8985
ForceDirectories(ExpandConstant('{app}\config'));
9086
end;
9187
9288
function ServiceExists(ServiceName: string): Boolean;
9389
var
9490
ResultCode: Integer;
9591
begin
96-
// Check if service exists by trying to query it
9792
Result := Exec(ExpandConstant('{sys}\sc.exe'), 'query "' + ServiceName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
9893
end;
9994
100-
// Check for running processes before install
95+
function IsServiceRunning(ServiceName: string): Boolean;
96+
var
97+
ResultCode: Integer;
98+
Output: AnsiString;
99+
OutputFile: string;
100+
begin
101+
Result := False;
102+
OutputFile := ExpandConstant('{tmp}\service_status.txt');
103+
104+
// Query service status and capture output
105+
if Exec(ExpandConstant('{sys}\sc.exe'), 'query "' + ServiceName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
106+
begin
107+
// Use PowerShell to get service status more reliably
108+
if Exec(ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe'),
109+
'-Command "& {(Get-Service -Name ''' + ServiceName + ''' -ErrorAction SilentlyContinue).Status}"',
110+
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
111+
begin
112+
// If we can't determine status precisely, assume it might be running to be safe
113+
Result := True;
114+
end;
115+
end;
116+
end;
117+
118+
function WaitForServiceStop(ServiceName: string): Boolean;
119+
var
120+
Counter: Integer;
121+
ResultCode: Integer;
122+
StatusOutput: AnsiString;
123+
TempFile: string;
124+
begin
125+
Result := True;
126+
Counter := 0;
127+
TempFile := ExpandConstant('{tmp}\service_check.txt');
128+
129+
// Wait up to 30 seconds for service to stop
130+
while Counter < 30 do
131+
begin
132+
// Check service status using PowerShell for more reliable output
133+
if Exec(ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe'),
134+
'-Command "& {try { $s = Get-Service -Name ''' + ServiceName + ''' -ErrorAction Stop; $s.Status } catch { ''NotFound'' }}" > "' + TempFile + '"',
135+
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
136+
begin
137+
if LoadStringFromFile(TempFile, StatusOutput) then
138+
begin
139+
// If service is stopped or not found, we're good
140+
if (Pos('Stopped', StatusOutput) > 0) or (Pos('NotFound', StatusOutput) > 0) then
141+
begin
142+
DeleteFile(TempFile);
143+
Exit;
144+
end;
145+
end;
146+
end;
147+
148+
Sleep(1000);
149+
Counter := Counter + 1;
150+
end;
151+
152+
// Cleanup temp file
153+
DeleteFile(TempFile);
154+
155+
// If we get here, service didn't stop in time
156+
if Counter >= 30 then
157+
begin
158+
MsgBox('Warning: Service took longer than expected to stop. Installation will continue but the service may need to be restarted manually.',
159+
mbInformation, MB_OK);
160+
Result := False;
161+
end;
162+
end;
163+
101164
function InitializeSetup(): Boolean;
102165
var
103166
ResultCode: Integer;
104167
begin
105-
// Try to stop the service if it's running
106-
if ServiceExists('{#MyServiceName}') then
168+
// Remember if service existed before installation
169+
WasServiceExisting := ServiceExists('{#MyServiceName}');
170+
171+
// Only stop service if it exists and is running
172+
if WasServiceExisting and IsServiceRunning('{#MyServiceName}') then
107173
begin
108-
Exec(ExpandConstant('{sys}\sc.exe'), 'stop "{#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
109-
Sleep(3000);
174+
if MsgBox('Cleanuparr service is currently running and needs to be stopped for the installation. Continue?',
175+
mbConfirmation, MB_YESNO) = IDYES then
176+
begin
177+
Exec(ExpandConstant('{sys}\sc.exe'), 'stop "{#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
178+
if not WaitForServiceStop('{#MyServiceName}') then
179+
begin
180+
// Service didn't stop properly, but continue anyway
181+
Log('Warning: Service did not stop cleanly, continuing with installation');
182+
end;
183+
end
184+
else
185+
begin
186+
Result := False;
187+
Exit;
188+
end;
110189
end;
190+
111191
Result := True;
112192
end;
113193
114-
// Handle cleanup before uninstall
115194
function InitializeUninstall(): Boolean;
116195
var
117196
ResultCode: Integer;
118197
begin
119-
// Stop the service before uninstalling
120198
if ServiceExists('{#MyServiceName}') then
121199
begin
122-
Exec(ExpandConstant('{sys}\sc.exe'), 'stop "{#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
123-
Sleep(3000);
200+
if MsgBox('Cleanuparr service will be stopped and removed. Continue with uninstallation?',
201+
mbConfirmation, MB_YESNO) = IDYES then
202+
begin
203+
Exec(ExpandConstant('{sys}\sc.exe'), 'stop "{#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
204+
WaitForServiceStop('{#MyServiceName}');
205+
end
206+
else
207+
begin
208+
Result := False;
209+
Exit;
210+
end;
124211
end;
125212
Result := True;
126-
end;
213+
end;

0 commit comments

Comments
 (0)