Skip to content

Commit 099a219

Browse files
committed
feat(client): Implement the mechanism for updating the guest server
1 parent 224f5be commit 099a219

6 files changed

Lines changed: 134 additions & 14 deletions

File tree

build-guest-server.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ export LDFLAGS=(
1515
"-X 'main.BuildTimestamp=${BUILD_TIMESTAMP}'"
1616
)
1717

18+
echo "Version: ${VERSION}"
19+
echo "Commit Hash: ${COMMIT_HASH}"
20+
echo "Build Timestamp: ${BUILD_TIMESTAMP}"
21+
1822
cd guest_server
1923
go build -ldflags="${LDFLAGS[*]}" -o winboat_guest_server.exe main.go
2024
rm -f winboat_guest_server.zip

bun.lock

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "winboat",
3-
"version": "0.6.0",
3+
"version": "0.6.1",
44
"description": "Windows for Penguins",
55
"main": "main/main.js",
66
"scripts": {
@@ -36,6 +36,7 @@
3636
"@vueuse/core": "^13.1.0",
3737
"@vueuse/motion": "^2.2.6",
3838
"apexcharts": "^4.5.0",
39+
"form-data": "^4.0.4",
3940
"json-to-pretty-yaml": "^1.2.2",
4041
"marked": "^15.0.6",
4142
"nanoevents": "^9.1.0",

src/renderer/App.vue

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
11
<template>
2-
<main class="h-screen w-screen overflow-hidden relative">
2+
<main class="overflow-hidden relative w-screen h-screen">
33
<!-- Decoration -->
44
<div class="gradient-ball absolute -z-10 left-0 bottom-0 translate-x-[-50%] translate-y-[50%] w-[90vw] aspect-square opacity-15 blob-anim"></div>
55
<div class="gradient-ball absolute -z-10 right-0 top-0 translate-x-[50%] translate-y-[-50%] w-[90vw] aspect-square opacity-15 blob-anim"></div>
66

77
<!-- Titlebar -->
8-
<x-titlebar @minimize="handleMinimize()" @buttonclick="handleTitleBarEvent" class="bg-neutral-900/50 backdrop-blur-xl">
8+
<x-titlebar @minimize="handleMinimize()" @buttonclick="handleTitleBarEvent" class="backdrop-blur-xl bg-neutral-900/50">
99
<x-label>WinBoat</x-label>
1010
</x-titlebar>
1111

12+
<!-- Updater -->
13+
<dialog ref="updateDialog">
14+
<Icon class="text-indigo-400 size-12" icon="mdi:cloud-upload"></Icon>
15+
<h3 class="mt-2" v-if="winboat?.isUpdatingGuestServer.value">Updating Guest Server</h3>
16+
<h3 class="mt-2" v-else>Guest Server update successful!</h3>
17+
<p v-if="winboat?.isUpdatingGuestServer.value" class="max-w-[40vw]">
18+
The guest is currently running an outdated version of the WinBoat Guest Server. Please wait while we update it to the current version.
19+
</p>
20+
<p v-else class="max-w-[40vw]">
21+
The WinBoat Guest Server has been updated successfully! You can now close this dialog and continue using the application.
22+
</p>
23+
<footer>
24+
<x-progressbar v-if="winboat?.isUpdatingGuestServer.value" class="my-4"></x-progressbar>
25+
<x-button v-else id="close-button" @click="updateDialog!.close()" toggled>
26+
<x-label>Close</x-label>
27+
</x-button>
28+
</footer>
29+
</dialog>
1230

1331
<!-- UI / SetupUI -->
1432
<div v-if="useRoute().name !== 'SetupUI'" class="flex flex-row h-[calc(100vh-2rem)]">
15-
<x-nav class="w-72 flex flex-none flex-col gap-0.5 bg-gray-500/10 backdrop-contrast-90 backdrop-blur-xl">
33+
<x-nav class="flex flex-col flex-none gap-0.5 w-72 backdrop-blur-xl bg-gray-500/10 backdrop-contrast-90">
1634
<div
1735
v-if="winboat?.rdpConnected.value"
1836
class="w-full bg-gradient-to-r from-indigo-500 via-indigo-400 to-blue-500 text-white
@@ -23,8 +41,8 @@
2341
RDP Session Active
2442
</span>
2543
</div>
26-
<div class="p-4 flex flex-row items-center gap-4">
27-
<img class="rounded-full w-16"
44+
<div class="flex flex-row gap-4 items-center p-4">
45+
<img class="w-16 rounded-full"
2846
src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b5/Windows_10_Default_Profile_Picture.svg/2048px-Windows_10_Default_Profile_Picture.svg.png"
2947
alt="Profile Picture">
3048
<div>
@@ -34,23 +52,23 @@
3452
</div>
3553
<RouterLink v-for="route of routes.filter(r => !['SetupUI', 'Loading'].includes(r.name))" :to="route.path" :key="route.path">
3654
<x-navitem value="first">
37-
<Icon class="w-5 h-5 mr-4" :icon="(route.meta!.icon as string)"></Icon>
55+
<Icon class="mr-4 w-5 h-5" :icon="(route.meta!.icon as string)"></Icon>
3856
<x-label>{{ route.name }}</x-label>
3957
</x-navitem>
4058
</RouterLink>
41-
<div class="flex flex-col items-center justify-end h-full p-4">
59+
<div class="flex flex-col justify-end items-center p-4 h-full">
4260
<p class="text-xs text-neutral-500">WinBoat Pre-Release Alpha {{ appVer }}</p>
4361
</div>
4462
</x-nav>
4563
<div class="px-5 flex-grow max-h-[calc(100vh-2rem)] overflow-y-auto py-4">
46-
<div class="flex flex-row items-center gap-2 my-6">
64+
<div class="flex flex-row gap-2 items-center my-6">
4765
<Icon class="w-6 h-6 opacity-60" icon="icon-park-solid:toolkit"></Icon>
48-
<h1 class="text-2xl font-semibold opacity-60 my-0">
66+
<h1 class="my-0 text-2xl font-semibold opacity-60">
4967
WinBoat
5068
</h1>
5169
<Icon class="w-6 h-6" icon="bitcoin-icons:caret-right-filled"></Icon>
5270
<Icon class="w-6 h-6" :icon="(useRoute().meta.icon as string)"></Icon>
53-
<h1 class="text-2xl font-semibold my-0">
71+
<h1 class="my-0 text-2xl font-semibold">
5472
{{ useRoute().name }}
5573
</h1>
5674
</div>
@@ -72,7 +90,7 @@
7290
import { RouterLink, useRoute, useRouter } from 'vue-router';
7391
import { routes } from './router';
7492
import { Icon } from '@iconify/vue';
75-
import { onMounted } from 'vue';
93+
import { onMounted, useTemplateRef, watch } from 'vue';
7694
import { isInstalled } from './lib/install';
7795
import { Winboat } from './lib/winboat';
7896
const { BrowserWindow }: typeof import('@electron/remote') = require('@electron/remote')
@@ -81,6 +99,7 @@ const path: typeof import('path') = require('path')
8199
const remote: typeof import('@electron/remote') = require('@electron/remote');
82100
83101
const $router = useRouter();
102+
const updateDialog = useTemplateRef('updateDialog');
84103
const appVer = import.meta.env.VITE_APP_VERSION;
85104
let winboat: Winboat | null = null;
86105
@@ -94,6 +113,13 @@ onMounted(async () => {
94113
winboat = new Winboat();
95114
$router.push('/home');
96115
}
116+
117+
// Watch for guest server updates and show dialog
118+
watch(() => winboat?.isUpdatingGuestServer.value, (newVal) => {
119+
if (newVal) {
120+
updateDialog.value!.showModal();
121+
}
122+
})
97123
})
98124
99125
function handleMinimize() {
@@ -122,6 +148,11 @@ function handleTitleBarEvent(e: CustomEvent) {
122148
</script>
123149

124150
<style>
151+
dialog::backdrop {
152+
pointer-events: none;
153+
backdrop-filter: blur(8px);
154+
}
155+
125156
.gradient-ball {
126157
border-radius: 99999px;
127158
background: linear-gradient(197.37deg, #7450DB -0.38%, rgba(138, 234, 240, 0) 101.89%), linear-gradient(115.93deg, #3E88F6 4.86%, rgba(62, 180, 246, 0.33) 38.05%, rgba(62, 235, 246, 0) 74.14%), radial-gradient(56.47% 76.87% at 6.92% 7.55%, rgba(62, 136, 246, 0.7) 0%, rgba(62, 158, 246, 0.182) 52.16%, rgba(62, 246, 246, 0) 100%), linear-gradient(306.53deg, #2EE4E3 19.83%, rgba(46, 228, 227, 0) 97.33%);

src/renderer/lib/winboat.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ref, type Ref } from "vue";
22
import { WINBOAT_DIR, WINBOAT_GUEST_API } from "./constants";
3-
import type { ComposeConfig, Metrics, WinApp } from "../../types";
3+
import type { ComposeConfig, GuestServerUpdateResponse, GuestServerVersion, Metrics, WinApp } from "../../types";
44
import { createLogger } from "../utils/log";
55
import { AppIcons } from "../data/appicons";
66
import YAML from 'yaml';
@@ -13,6 +13,8 @@ const path: typeof import('path') = require('path');
1313
const fs: typeof import('fs') = require('fs');
1414
const { promisify }: typeof import('util') = require('util');
1515
const { exec }: typeof import('child_process') = require('child_process');
16+
const remote: typeof import('@electron/remote') = require('@electron/remote');
17+
const FormData: typeof import('form-data') = require('form-data');
1618

1719
const execAsync = promisify(exec);
1820

@@ -117,6 +119,7 @@ class AppManager {
117119
export class Winboat {
118120
#healthInterval: NodeJS.Timeout | null = null;
119121
isOnline: Ref<boolean> = ref(false);
122+
isUpdatingGuestServer: Ref<boolean> = ref(false);
120123
#containerInterval: NodeJS.Timeout | null = null;
121124
containerStatus: Ref<ContainerStatusValue> = ref(ContainerStatus.Exited)
122125
containerActionLoading: Ref<boolean> = ref(false)
@@ -189,6 +192,10 @@ export class Winboat {
189192
if (_isOnline !== this.isOnline.value) {
190193
this.isOnline.value = _isOnline;
191194
logger.info(`Winboat Guest API went ${this.isOnline ? 'online' : 'offline'}`);
195+
196+
if (this.isOnline.value) {
197+
await this.checkVersionAndUpdateGuestServer();
198+
}
192199
}
193200
}, HEALTH_WAIT_MS);
194201

@@ -464,4 +471,64 @@ export class Winboat {
464471

465472
await execAsync(cmd);
466473
}
474+
475+
async checkVersionAndUpdateGuestServer() {
476+
// 1. Get the version of the guest server and compare it to the current version
477+
const versionRes = await nodeFetch(`${WINBOAT_GUEST_API}/version`);
478+
const version = await versionRes.json() as GuestServerVersion;
479+
480+
const appVersion = import.meta.env.VITE_APP_VERSION;
481+
482+
if (version.version !== appVersion) {
483+
logger.info(`New local version of WinBoat Guest Server found: ${appVersion}`);
484+
logger.info(`Current version of WinBoat Guest Server: ${version.version}`);
485+
}
486+
487+
// 2. Return early if the version is the same
488+
if (version.version === appVersion) {
489+
return;
490+
}
491+
492+
// 3. Set update flag & grab winboat_guest_server.zip from Electron assets
493+
this.isUpdatingGuestServer.value = true;
494+
const zipPath = remote.app.isPackaged
495+
? path.join(remote.app.getAppPath(), 'guest_server', 'winboat_guest_server.zip')
496+
: path.join(remote.app.getAppPath(), '..', '..', 'guest_server', 'winboat_guest_server.zip');
497+
498+
// 4. Send the payload to the guest server, as a multipart/form-data with updateFile
499+
const formData = new FormData();
500+
formData.append('updateFile', fs.createReadStream(zipPath));
501+
502+
try {
503+
const res = await nodeFetch(`${WINBOAT_GUEST_API}/update`, {
504+
method: 'POST',
505+
body: formData as any
506+
});
507+
if (res.status !== 200) {
508+
const resBody = await res.text();
509+
throw new Error(resBody);
510+
}
511+
const resJson = await res.json() as GuestServerUpdateResponse;
512+
logger.info(`Update params: ${JSON.stringify(resJson, null, 4)}`);
513+
logger.info("Successfully sent update payload to guest server");
514+
515+
} catch(e) {
516+
logger.error("Failed to send update payload to guest server");
517+
logger.error(e);
518+
this.isUpdatingGuestServer.value = false;
519+
throw e;
520+
}
521+
522+
// 5. Wait about ~3 seconds, then start scanning for health
523+
await new Promise(resolve => setTimeout(resolve, 3000));
524+
let _isOnline = await this.getHealth();
525+
while (!_isOnline) {
526+
await new Promise(resolve => setTimeout(resolve, 1000));
527+
_isOnline = await this.getHealth();
528+
}
529+
logger.info("Update completed, Winboat Guest Server is online");
530+
531+
// Done!
532+
this.isUpdatingGuestServer.value = false;
533+
}
467534
}

src/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,16 @@ export type Metrics = {
7777
total: number, // Disk Total in MB (e.g. 102400)
7878
percentage: number // Disk Usage in percentage (e.g. 70%)
7979
}
80+
}
81+
82+
export type GuestServerVersion = {
83+
version: string;
84+
commit_hash: string;
85+
build_time: string;
86+
}
87+
88+
export type GuestServerUpdateResponse = {
89+
filename: string;
90+
status: string;
91+
temp_path: string;
8092
}

0 commit comments

Comments
 (0)