mirror of
https://github.com/kasmtech/kasm-install-wizard.git
synced 2026-07-17 16:35:08 +00:00
KASM-8264 Registry based wizard installer
This commit is contained in:
parent
502bc2f530
commit
d4b7db69f4
3 changed files with 98 additions and 5 deletions
92
index.js
92
index.js
|
|
@ -24,15 +24,100 @@ const { spawn } = require('node:child_process');
|
||||||
var EULA;
|
var EULA;
|
||||||
var images;
|
var images;
|
||||||
var currentVersion;
|
var currentVersion;
|
||||||
|
var sourceLocal = false;
|
||||||
var gpuInfo;
|
var gpuInfo;
|
||||||
var installSettings = {};
|
var installSettings = {};
|
||||||
var upgradeSettings = {};
|
var upgradeSettings = {};
|
||||||
|
|
||||||
|
// Find the best matching compatibility entry for the current version.
|
||||||
|
// Exact version match takes precedence over wildcard (e.g. "1.18.x").
|
||||||
|
function matchVersion(currentVer, compatibilityList) {
|
||||||
|
if (!compatibilityList || compatibilityList.length === 0) return null;
|
||||||
|
const parts = currentVer.split('.');
|
||||||
|
const major = parts[0];
|
||||||
|
const minor = parts[1] !== undefined ? parts[1] : '0';
|
||||||
|
|
||||||
|
// Exact match (most precise)
|
||||||
|
let match = compatibilityList.find(c => c.version === currentVer);
|
||||||
|
if (match) return match;
|
||||||
|
|
||||||
|
// Major.minor wildcard, e.g. "1.18.x"
|
||||||
|
match = compatibilityList.find(c => c.version === `${major}.${minor}.x`);
|
||||||
|
if (match) return match;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch workspace list from registry and convert to the images YAML structure.
|
||||||
|
// Falls back to local list.json if the remote is unavailable.
|
||||||
|
async function fetchWorkspaceList(currentVer, archName) {
|
||||||
|
let listData;
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://registry.kasmweb.com/1.1/list.json');
|
||||||
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
|
listData = await response.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch workspace list, falling back to local list.json:', err.message);
|
||||||
|
let localList = await fsw.readFile('/wizard/list.json', 'utf8');
|
||||||
|
listData = JSON.parse(localList);
|
||||||
|
sourceLocal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultChannel = listData.default_channel;
|
||||||
|
const filtered = (listData.workspaces || []).filter(
|
||||||
|
ws => ws.architecture && ws.architecture.includes(archName)
|
||||||
|
);
|
||||||
|
|
||||||
|
let imageIdx = 1;
|
||||||
|
const imagesList = [];
|
||||||
|
for (const ws of filtered) {
|
||||||
|
const compat = matchVersion(currentVer, ws.compatibility || []);
|
||||||
|
if (!compat) continue;
|
||||||
|
|
||||||
|
// Use default_channel tag if it exists in available_tags, otherwise keep compat image tag
|
||||||
|
let imageName = compat.image;
|
||||||
|
const imageBase = imageName.split(':')[0];
|
||||||
|
if (defaultChannel && compat.available_tags && compat.available_tags.includes(defaultChannel)) {
|
||||||
|
imageName = imageBase + ':' + defaultChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesList.push({
|
||||||
|
categories: ws.categories,
|
||||||
|
cores: 2.0,
|
||||||
|
cpu_allocation_method: 'Inherit',
|
||||||
|
description: ws.description,
|
||||||
|
docker_registry: ws.docker_registry,
|
||||||
|
enabled: true,
|
||||||
|
exec_config: {},
|
||||||
|
friendly_name: ws.friendly_name,
|
||||||
|
gpu_count: 0,
|
||||||
|
hidden: false,
|
||||||
|
image_id: '${uuid:image_id:' + imageIdx + '}',
|
||||||
|
image_src: 'https://registry.kasmweb.com/1.1/icons/' + ws.image_src,
|
||||||
|
image_type: 'Container',
|
||||||
|
launch_config: {},
|
||||||
|
memory: 2768000000,
|
||||||
|
name: imageName,
|
||||||
|
notes: ws.notes || null,
|
||||||
|
run_config: {},
|
||||||
|
uncompressed_size_mb: compat.uncompressed_size_mb,
|
||||||
|
zone_id: null
|
||||||
|
});
|
||||||
|
imageIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { alembic_version: 'e3900d8a4fee', images: imagesList };
|
||||||
|
}
|
||||||
|
|
||||||
// Grab installer variables
|
// Grab installer variables
|
||||||
async function installerBlobs() {
|
async function installerBlobs() {
|
||||||
EULA = await fsw.readFile('/kasm_release/licenses/LICENSE.txt', 'utf8');
|
EULA = await fsw.readFile('/kasm_release/licenses/LICENSE.txt', 'utf8');
|
||||||
let imagesText = await fsw.readFile('/wizard/default_images_' + arch + '.yaml', 'utf8');
|
try {
|
||||||
images = yaml.load(imagesText);
|
|
||||||
currentVersion = fs.readFileSync('/version.txt', 'utf8').replace(/(\r\n|\n|\r)/gm,'');
|
currentVersion = fs.readFileSync('/version.txt', 'utf8').replace(/(\r\n|\n|\r)/gm,'');
|
||||||
|
} catch (err) {
|
||||||
|
currentVersion = '1.18.1!';
|
||||||
|
}
|
||||||
|
images = await fetchWorkspaceList(currentVersion, arch);
|
||||||
let gpuData = [];
|
let gpuData = [];
|
||||||
let gpuCmd = spawn('/gpuinfo.sh');
|
let gpuCmd = spawn('/gpuinfo.sh');
|
||||||
gpuCmd.stdout.on('data', function(data) {
|
gpuCmd.stdout.on('data', function(data) {
|
||||||
|
|
@ -192,6 +277,7 @@ io.on('connection', async function (socket) {
|
||||||
dashinfo['localVersion'] = 'Unknown';
|
dashinfo['localVersion'] = 'Unknown';
|
||||||
}
|
}
|
||||||
dashinfo['currentVersion'] = currentVersion;
|
dashinfo['currentVersion'] = currentVersion;
|
||||||
|
dashinfo['sourceLocal'] = sourceLocal;
|
||||||
dashinfo['gpuInfo'] = gpuInfo;
|
dashinfo['gpuInfo'] = gpuInfo;
|
||||||
dashinfo['containers'] = containers;
|
dashinfo['containers'] = containers;
|
||||||
dashinfo['cpu'] = await si.cpu();
|
dashinfo['cpu'] = await si.cpu();
|
||||||
|
|
@ -201,7 +287,7 @@ io.on('connection', async function (socket) {
|
||||||
socket.emit('renderdash', [dashinfo, images]);
|
socket.emit('renderdash', [dashinfo, images]);
|
||||||
// Render installer
|
// Render installer
|
||||||
} else {
|
} else {
|
||||||
socket.emit('renderinstall', [EULA, images, gpuInfo]);
|
socket.emit('renderinstall', [EULA, images, gpuInfo, currentVersion, sourceLocal]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Disable wizard
|
// Disable wizard
|
||||||
|
|
|
||||||
1
list.json
Normal file
1
list.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -88,6 +88,10 @@ function titleChange(value) {
|
||||||
$('#title').text(value);
|
$('#title').text(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function versionChange(ver, local) {
|
||||||
|
document.title = 'Kasm Wizard ' + ver + (local ? ' (local)' : '');
|
||||||
|
}
|
||||||
|
|
||||||
// Render landing as installer page
|
// Render landing as installer page
|
||||||
function renderInstall(data) {
|
function renderInstall(data) {
|
||||||
showContainer();
|
showContainer();
|
||||||
|
|
@ -95,6 +99,7 @@ function renderInstall(data) {
|
||||||
EULA = data[0];
|
EULA = data[0];
|
||||||
images = data[1];
|
images = data[1];
|
||||||
gpus = data[2];
|
gpus = data[2];
|
||||||
|
versionChange(data[3], data[4]);
|
||||||
let EULADiv = $('<div>', {id: 'EULA'}).text(EULA);
|
let EULADiv = $('<div>', {id: 'EULA'}).text(EULA);
|
||||||
$('#container').append(EULADiv);
|
$('#container').append(EULADiv);
|
||||||
let EULAButton = $('<button>', {id: 'EULAButton', onclick: 'pickSettings()', class: 'btn btn-default btn-ghost'}).text('Accept and continue');
|
let EULAButton = $('<button>', {id: 'EULAButton', onclick: 'pickSettings()', class: 'btn btn-default btn-ghost'}).text('Accept and continue');
|
||||||
|
|
@ -107,6 +112,7 @@ async function renderDash(data) {
|
||||||
titleChange('Dashboard');
|
titleChange('Dashboard');
|
||||||
let info = data[0];
|
let info = data[0];
|
||||||
images = data[1];
|
images = data[1];
|
||||||
|
versionChange(info.currentVersion, info.sourceLocal);
|
||||||
// Store GPU info
|
// Store GPU info
|
||||||
$('body').data('gpuInfo', info.gpuInfo);
|
$('body').data('gpuInfo', info.gpuInfo);
|
||||||
// Upgrade button if needed
|
// Upgrade button if needed
|
||||||
|
|
@ -319,7 +325,7 @@ async function pickImages(upgrade) {
|
||||||
title: image.description,
|
title: image.description,
|
||||||
onclick: 'selectImage(\'' + image.friendly_name.replace(new RegExp(' ', 'g'), '_').replace('.', '-') + '\')'
|
onclick: 'selectImage(\'' + image.friendly_name.replace(new RegExp(' ', 'g'), '_').replace('.', '-') + '\')'
|
||||||
}).append(imageName).css('filter', 'grayscale(100%)')
|
}).append(imageName).css('filter', 'grayscale(100%)')
|
||||||
let thumb = $('<img>', {class: 'thumb', src: 'public/' + image.image_src});
|
let thumb = $('<img>', {class: 'thumb', src: image.image_src, loading: 'lazy'});
|
||||||
imageDiv.append(thumb);
|
imageDiv.append(thumb);
|
||||||
$('#images').append(imageDiv);
|
$('#images').append(imageDiv);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue