Skip to content

Commit b8cfc3f

Browse files
andrasbacsaiclaude
andcommitted
Add real-time upgrade progress tracking via status file
- upgrade.sh now writes status to /data/coolify/source/.upgrade-status - New /api/upgrade-status endpoint reads status file for real progress - Frontend polls status API instead of simulating progress - Falls back to health check when service goes down during restart 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0aa7e37 commit b8cfc3f

File tree

4 files changed

+147
-46
lines changed

4 files changed

+147
-46
lines changed

app/Http/Controllers/Api/OtherController.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,82 @@ public function healthcheck(Request $request)
186186
{
187187
return 'OK';
188188
}
189+
190+
#[OA\Get(
191+
summary: 'Upgrade Status',
192+
description: 'Get the current upgrade status. Returns the step and message from the upgrade process.',
193+
path: '/upgrade-status',
194+
operationId: 'upgrade-status',
195+
responses: [
196+
new OA\Response(
197+
response: 200,
198+
description: 'Returns upgrade status.',
199+
content: new OA\JsonContent(
200+
type: 'object',
201+
properties: [
202+
new OA\Property(property: 'status', type: 'string', example: 'in_progress'),
203+
new OA\Property(property: 'step', type: 'integer', example: 3),
204+
new OA\Property(property: 'message', type: 'string', example: 'Pulling Docker images'),
205+
new OA\Property(property: 'timestamp', type: 'string', example: '2024-01-15T10:30:45+00:00'),
206+
]
207+
)),
208+
new OA\Response(
209+
response: 400,
210+
ref: '#/components/responses/400',
211+
),
212+
]
213+
)]
214+
public function upgradeStatus(Request $request)
215+
{
216+
$statusFile = '/data/coolify/source/.upgrade-status';
217+
218+
if (! file_exists($statusFile)) {
219+
return response()->json(['status' => 'none']);
220+
}
221+
222+
$content = trim(file_get_contents($statusFile));
223+
if (empty($content)) {
224+
return response()->json(['status' => 'none']);
225+
}
226+
227+
$parts = explode('|', $content);
228+
if (count($parts) < 3) {
229+
return response()->json(['status' => 'none']);
230+
}
231+
232+
[$step, $message, $timestamp] = $parts;
233+
234+
// Check if status is stale (older than 30 minutes)
235+
try {
236+
$statusTime = new \DateTime($timestamp);
237+
$now = new \DateTime;
238+
$diffMinutes = ($now->getTimestamp() - $statusTime->getTimestamp()) / 60;
239+
240+
if ($diffMinutes > 30) {
241+
return response()->json(['status' => 'none']);
242+
}
243+
} catch (\Exception $e) {
244+
// If timestamp parsing fails, continue with the status
245+
}
246+
247+
// Determine status based on step
248+
if ($step === 'error') {
249+
return response()->json([
250+
'status' => 'error',
251+
'step' => 0,
252+
'message' => $message,
253+
'timestamp' => $timestamp,
254+
]);
255+
}
256+
257+
$stepInt = (int) $step;
258+
$status = $stepInt >= 6 ? 'complete' : 'in_progress';
259+
260+
return response()->json([
261+
'status' => $status,
262+
'step' => $stepInt,
263+
'message' => $message,
264+
'timestamp' => $timestamp,
265+
]);
266+
}
189267
}

resources/views/livewire/upgrade.blade.php

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
161161
showProgress: false,
162162
currentStatus: '',
163163
checkHealthInterval: null,
164-
checkIfIamDeadInterval: null,
164+
checkUpgradeStatusInterval: null,
165165
elapsedInterval: null,
166166
healthCheckAttempts: 0,
167167
startTime: null,
@@ -171,10 +171,12 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
171171
successCountdown: 3,
172172
currentVersion: config.currentVersion || '',
173173
latestVersion: config.latestVersion || '',
174+
serviceDown: false,
174175
175176
confirmed() {
176177
this.showProgress = true;
177178
this.currentStep = 1;
179+
this.currentStatus = 'Starting upgrade...';
178180
this.startTimer();
179181
this.$wire.$call('upgrade');
180182
this.upgrade();
@@ -197,14 +199,14 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
197199
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
198200
},
199201
200-
getStepMessage(step) {
201-
const messages = {
202-
1: 'Preparing upgrade...',
203-
2: 'Pulling helper image...',
204-
3: 'Pulling Coolify image...',
205-
4: 'Restarting Coolify...'
206-
};
207-
return messages[step] || 'Processing...';
202+
mapStepToUI(apiStep) {
203+
// Map backend steps (1-6) to UI steps (1-4)
204+
// Backend: 1=config, 2=env, 3=pull, 4=stop, 5=start, 6=complete
205+
// UI: 1=prepare, 2=pull images, 3=pull coolify, 4=restart
206+
if (apiStep <= 2) return 1;
207+
if (apiStep === 3) return 2;
208+
if (apiStep <= 5) return 3;
209+
return 4;
208210
},
209211
210212
getReviveStatusMessage(elapsedMinutes, attempts) {
@@ -249,6 +251,10 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
249251
clearInterval(this.checkHealthInterval);
250252
this.checkHealthInterval = null;
251253
}
254+
if (this.checkUpgradeStatusInterval) {
255+
clearInterval(this.checkUpgradeStatusInterval);
256+
this.checkUpgradeStatusInterval = null;
257+
}
252258
if (this.elapsedInterval) {
253259
clearInterval(this.elapsedInterval);
254260
this.elapsedInterval = null;
@@ -273,52 +279,43 @@ class="w-24 dark:bg-coolgray-200 dark:hover:bg-coolgray-300">Cancel
273279
},
274280
275281
upgrade() {
276-
if (this.checkIfIamDeadInterval) return true;
282+
if (this.checkUpgradeStatusInterval) return true;
277283
this.currentStep = 1;
278-
this.currentStatus = this.getStepMessage(1);
279-
280-
// Simulate step progression (since we can't get real-time feedback from Docker pulls)
281-
let stepTime = 0;
282-
const stepInterval = setInterval(() => {
283-
stepTime++;
284-
// Progress through steps based on elapsed time
285-
if (stepTime >= 3 && this.currentStep === 1) {
286-
this.currentStep = 2;
287-
this.currentStatus = this.getStepMessage(2);
288-
} else if (stepTime >= 8 && this.currentStep === 2) {
289-
this.currentStep = 3;
290-
this.currentStatus = this.getStepMessage(3);
291-
}
292-
}, 1000);
284+
this.currentStatus = 'Starting upgrade...';
285+
this.serviceDown = false;
293286
294-
this.checkIfIamDeadInterval = setInterval(() => {
295-
fetch('/api/health')
287+
// Poll upgrade status API for real progress
288+
this.checkUpgradeStatusInterval = setInterval(() => {
289+
fetch('/api/upgrade-status')
296290
.then(response => {
297291
if (response.ok) {
298-
// Still running, update status based on current step
299-
this.currentStatus = this.getStepMessage(this.currentStep);
300-
} else {
301-
// Service is down, now waiting to revive
302-
clearInterval(stepInterval);
292+
return response.json();
293+
}
294+
throw new Error('Service unavailable');
295+
})
296+
.then(data => {
297+
if (data.status === 'in_progress') {
298+
this.currentStep = this.mapStepToUI(data.step);
299+
this.currentStatus = data.message;
300+
} else if (data.status === 'complete') {
301+
this.showSuccess();
302+
} else if (data.status === 'error') {
303+
this.currentStatus = `Error: ${data.message}`;
304+
}
305+
})
306+
.catch(error => {
307+
// Service is down - switch to health check mode
308+
console.log('Upgrade status API unavailable, switching to health check mode');
309+
if (!this.serviceDown) {
310+
this.serviceDown = true;
303311
this.currentStep = 4;
304312
this.currentStatus = 'Coolify is restarting with the new version...';
305-
if (this.checkIfIamDeadInterval) {
306-
clearInterval(this.checkIfIamDeadInterval);
307-
this.checkIfIamDeadInterval = null;
313+
if (this.checkUpgradeStatusInterval) {
314+
clearInterval(this.checkUpgradeStatusInterval);
315+
this.checkUpgradeStatusInterval = null;
308316
}
309317
this.revive();
310318
}
311-
})
312-
.catch(error => {
313-
console.error('Health check failed:', error);
314-
clearInterval(stepInterval);
315-
this.currentStep = 4;
316-
this.currentStatus = 'Coolify is restarting with the new version...';
317-
if (this.checkIfIamDeadInterval) {
318-
clearInterval(this.checkIfIamDeadInterval);
319-
this.checkIfIamDeadInterval = null;
320-
}
321-
this.revive();
322319
});
323320
}, 2000);
324321
}

routes/api.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
use Illuminate\Support\Facades\Route;
2020

2121
Route::get('/health', [OtherController::class, 'healthcheck']);
22+
Route::get('/upgrade-status', [OtherController::class, 'upgradeStatus']);
2223
Route::group([
2324
'prefix' => 'v1',
2425
], function () {
2526
Route::get('/health', [OtherController::class, 'healthcheck']);
27+
Route::get('/upgrade-status', [OtherController::class, 'upgradeStatus']);
2628
});
2729

2830
Route::post('/feedback', [OtherController::class, 'feedback']);

scripts/upgrade.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ LATEST_HELPER_VERSION=${2:-latest}
77
REGISTRY_URL=${3:-ghcr.io}
88
SKIP_BACKUP=${4:-false}
99
ENV_FILE="/data/coolify/source/.env"
10+
STATUS_FILE="/data/coolify/source/.upgrade-status"
1011

1112
DATE=$(date +%Y-%m-%d-%H-%M-%S)
1213
LOGFILE="/data/coolify/source/upgrade-${DATE}.log"
@@ -24,6 +25,13 @@ log_section() {
2425
echo "============================================================" >>"$LOGFILE"
2526
}
2627

28+
# Helper function to write upgrade status for API polling
29+
write_status() {
30+
local step="$1"
31+
local message="$2"
32+
echo "${step}|${message}|$(date -Iseconds)" > "$STATUS_FILE"
33+
}
34+
2735
echo ""
2836
echo "=========================================="
2937
echo " Coolify Upgrade - ${DATE}"
@@ -40,6 +48,7 @@ echo "Registry URL: ${REGISTRY_URL}" >>"$LOGFILE"
4048
echo "============================================================" >>"$LOGFILE"
4149

4250
log_section "Step 1/6: Downloading configuration files"
51+
write_status "1" "Downloading configuration files"
4352
echo "1/6 Downloading latest configuration files..."
4453
log "Downloading docker-compose.yml from ${CDN}/docker-compose.yml"
4554
curl -fsSL -L $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml
@@ -63,6 +72,7 @@ if [ "$SKIP_BACKUP" != "true" ]; then
6372
fi
6473

6574
log_section "Step 2/6: Updating environment configuration"
75+
write_status "2" "Updating environment configuration"
6676
echo ""
6777
echo "2/6 Updating environment configuration..."
6878
log "Merging .env.production values into .env"
@@ -115,6 +125,7 @@ if [ -f /root/.docker/config.json ]; then
115125
fi
116126

117127
log_section "Step 3/6: Pulling Docker images"
128+
write_status "3" "Pulling Docker images"
118129
echo ""
119130
echo "3/6 Pulling Docker images..."
120131
echo " This may take a few minutes depending on your connection."
@@ -125,6 +136,7 @@ if docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE}" >>"
125136
log "Successfully pulled Coolify image"
126137
else
127138
log "ERROR: Failed to pull Coolify image"
139+
write_status "error" "Failed to pull Coolify image"
128140
echo " ERROR: Failed to pull Coolify image. Aborting upgrade."
129141
exit 1
130142
fi
@@ -135,6 +147,7 @@ if docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELP
135147
log "Successfully pulled Coolify helper image"
136148
else
137149
log "ERROR: Failed to pull Coolify helper image"
150+
write_status "error" "Failed to pull Coolify helper image"
138151
echo " ERROR: Failed to pull helper image. Aborting upgrade."
139152
exit 1
140153
fi
@@ -145,6 +158,7 @@ if docker pull postgres:15-alpine >>"$LOGFILE" 2>&1; then
145158
log "Successfully pulled PostgreSQL image"
146159
else
147160
log "ERROR: Failed to pull PostgreSQL image"
161+
write_status "error" "Failed to pull PostgreSQL image"
148162
echo " ERROR: Failed to pull PostgreSQL image. Aborting upgrade."
149163
exit 1
150164
fi
@@ -155,6 +169,7 @@ if docker pull redis:7-alpine >>"$LOGFILE" 2>&1; then
155169
log "Successfully pulled Redis image"
156170
else
157171
log "ERROR: Failed to pull Redis image"
172+
write_status "error" "Failed to pull Redis image"
158173
echo " ERROR: Failed to pull Redis image. Aborting upgrade."
159174
exit 1
160175
fi
@@ -165,6 +180,7 @@ if docker pull "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.10" >>"
165180
log "Successfully pulled Coolify realtime image"
166181
else
167182
log "ERROR: Failed to pull Coolify realtime image"
183+
write_status "error" "Failed to pull Coolify realtime image"
168184
echo " ERROR: Failed to pull realtime image. Aborting upgrade."
169185
exit 1
170186
fi
@@ -173,6 +189,7 @@ log "All images pulled successfully"
173189
echo " All images pulled successfully."
174190

175191
log_section "Step 4/6: Stopping and restarting containers"
192+
write_status "4" "Stopping containers"
176193
echo ""
177194
echo "4/6 Stopping containers and starting new ones..."
178195
echo " This step will restart all Coolify containers."
@@ -185,6 +202,7 @@ log "Starting container restart sequence (detached)..."
185202

186203
nohup bash -c "
187204
LOGFILE='$LOGFILE'
205+
STATUS_FILE='$STATUS_FILE'
188206
DOCKER_CONFIG_MOUNT='$DOCKER_CONFIG_MOUNT'
189207
REGISTRY_URL='$REGISTRY_URL'
190208
LATEST_HELPER_VERSION='$LATEST_HELPER_VERSION'
@@ -194,6 +212,10 @@ nohup bash -c "
194212
echo \"[\$(date '+%Y-%m-%d %H:%M:%S')] \$1\" >>\"\$LOGFILE\"
195213
}
196214
215+
write_status() {
216+
echo \"\$1|\$2|\$(date -Iseconds)\" > \"\$STATUS_FILE\"
217+
}
218+
197219
# Stop and remove containers
198220
for container in coolify coolify-db coolify-redis coolify-realtime; do
199221
if docker ps -a --format '{{.Names}}' | grep -q \"^\${container}\$\"; then
@@ -213,6 +235,7 @@ nohup bash -c "
213235
echo '============================================================' >>\"\$LOGFILE\"
214236
log 'Step 5/6: Starting new containers'
215237
echo '============================================================' >>\"\$LOGFILE\"
238+
write_status '5' 'Starting new containers'
216239
217240
if [ -f /data/coolify/source/docker-compose.custom.yml ]; then
218241
log 'Using custom docker-compose.yml'
@@ -230,6 +253,7 @@ nohup bash -c "
230253
echo '============================================================' >>\"\$LOGFILE\"
231254
log 'Step 6/6: Upgrade complete'
232255
echo '============================================================' >>\"\$LOGFILE\"
256+
write_status '6' 'Upgrade complete'
233257
log 'Coolify upgrade completed successfully'
234258
log \"Version: \${LATEST_IMAGE}\"
235259
echo '' >>\"\$LOGFILE\"

0 commit comments

Comments
 (0)