Revert "v0.4.65: Local directory ISO source (bind-mount workaround for Unraid)"
This reverts commit 72a2089c98.
This commit is contained in:
+12
-138
@@ -342,21 +342,13 @@
|
||||
},
|
||||
|
||||
storage: async () => {
|
||||
// v0.4.65: also fetch host NFS caps and local-dir registrations.
|
||||
// Caps tell us whether kernel mounts can possibly work; local
|
||||
// dirs are the bind-mount workaround for hosts that can't.
|
||||
const [isos, settings, nfsRes, disk, caps, localRes] = await Promise.all([
|
||||
const [isos, settings, nfsRes, disk] = await Promise.all([
|
||||
getJSON('/api/isos'), getJSON('/api/settings'), getJSON('/api/nfs'),
|
||||
getJSON('/api/storage/disk').catch(() => ({
|
||||
total_bytes: 0, available_bytes: 0, used_bytes: 0, path: '?',
|
||||
})),
|
||||
getJSON('/api/nfs/capabilities').catch(() => ({
|
||||
available: true, has_nfs3: true, has_nfs4: true, detail: '',
|
||||
})),
|
||||
getJSON('/api/local-dirs').catch(() => ({ directories: [] })),
|
||||
]);
|
||||
const mounts = nfsRes.mounts || [];
|
||||
const localDirs = localRes.directories || [];
|
||||
|
||||
// ── Upload card ──
|
||||
const drop = el('div', {class:'drop', id:'drop'}, [
|
||||
@@ -467,8 +459,6 @@
|
||||
isos.forEach(i => {
|
||||
const b = bootability(i, settings);
|
||||
const isNfs = i.source && i.source.kind === 'nfs';
|
||||
// v0.4.65: third source kind — bind-mounted host directory.
|
||||
const isLocalDir = i.source && i.source.kind === 'local_dir';
|
||||
const protectedNow = !!i.password_hash;
|
||||
|
||||
// The inline editor row is hidden by default; the Password
|
||||
@@ -588,9 +578,8 @@
|
||||
]),
|
||||
el('td', {class:'num'}, fmtBytes(i.size_bytes)),
|
||||
el('td', {},
|
||||
el('span', {class:'src-badge' + (isNfs ? ' nfs' : (isLocalDir ? ' nfs' : ''))},
|
||||
isNfs ? ('nfs:' + i.source.mount_id)
|
||||
: (isLocalDir ? ('dir:' + i.source.dir_id) : 'local'))),
|
||||
el('span', {class:'src-badge' + (isNfs ? ' nfs' : '')},
|
||||
isNfs ? ('nfs:' + i.source.mount_id) : 'local')),
|
||||
el('td', {},
|
||||
protectedNow
|
||||
? el('span', {class:'tag accent'}, 'protected')
|
||||
@@ -602,17 +591,11 @@
|
||||
}}, protectedNow ? 'Password ✎' : 'Set password'),
|
||||
isNfs
|
||||
? el('span', {class:'tag', style:'opacity:.6'}, 'on NFS')
|
||||
: (isLocalDir
|
||||
// v0.4.65: bytes live in an operator-managed
|
||||
// bind-mounted directory; OpenPXE shouldn't try to
|
||||
// delete files it didn't create. The next directory
|
||||
// re-scan would re-register them anyway.
|
||||
? el('span', {class:'tag', style:'opacity:.6'}, 'on disk')
|
||||
: el('button', {class:'danger', onclick: async () => {
|
||||
if (!confirm('Remove this image?')) return;
|
||||
await fetch('/api/isos/' + encodeURIComponent(i.id), {method:'DELETE'});
|
||||
render('storage');
|
||||
}}, 'Remove')),
|
||||
: el('button', {class:'danger', onclick: async () => {
|
||||
if (!confirm('Remove this image?')) return;
|
||||
await fetch('/api/isos/' + encodeURIComponent(i.id), {method:'DELETE'});
|
||||
render('storage');
|
||||
}}, 'Remove'),
|
||||
]),
|
||||
]);
|
||||
rowsAndEditors.push(tr, editorRow);
|
||||
@@ -702,95 +685,6 @@
|
||||
el('span'),
|
||||
])) : [el('div', {class:'empty'}, 'No NFS shares mounted.')];
|
||||
|
||||
// ── v0.4.65: Local directories (bind-mount workaround) ──
|
||||
// For hosts (Unraid is the dominant case) where the kernel
|
||||
// doesn't have NFS client modules loaded, in-container NFS
|
||||
// mounts simply can't succeed. The pragmatic answer is the same
|
||||
// one Bootimus / iVentoy / FOG use: mount the remote storage on
|
||||
// the *host* then bind-mount the path into the container.
|
||||
// OpenPXE reads from a regular directory — no protocol work, no
|
||||
// capabilities, no kernel module dependency.
|
||||
const ldPath = el('input', {type:'text', placeholder:'/mnt/external-isos'});
|
||||
const ldLabel = el('input', {type:'text', placeholder:'optional friendly name'});
|
||||
const ldMsg = el('div', {class:'msg'});
|
||||
const addLocalDir = el('button', {onclick: async () => {
|
||||
if (!ldPath.value) {
|
||||
ldMsg.replaceChildren(document.createTextNode('Path is required.'));
|
||||
ldMsg.className='msg err'; return;
|
||||
}
|
||||
ldMsg.replaceChildren(document.createTextNode('Adding…'));
|
||||
ldMsg.className='msg';
|
||||
const r = await postJSON('/api/local-dirs', {
|
||||
path: ldPath.value, label: ldLabel.value || null,
|
||||
});
|
||||
if (r.ok) {
|
||||
ldMsg.replaceChildren(document.createTextNode('Added.'));
|
||||
ldMsg.className='msg ok';
|
||||
render('storage');
|
||||
} else {
|
||||
const t = await r.text();
|
||||
ldMsg.replaceChildren(
|
||||
el('div', {}, [
|
||||
el('strong', {}, 'Add failed: '),
|
||||
document.createTextNode(t),
|
||||
]),
|
||||
);
|
||||
ldMsg.className='msg err';
|
||||
}
|
||||
}}, 'Add directory');
|
||||
|
||||
const ldRows = localDirs.length ? localDirs.map(d => el('div', {class: 'nfs-row' + (d.last_error ? ' down' : '')}, [
|
||||
el('span', {class: 'dot ' + (d.last_error ? 'err' : 'ok')}),
|
||||
el('div', {}, [
|
||||
el('div', {class:'id'}, d.label + ' · ' + d.path),
|
||||
el('div', {class:'meta'},
|
||||
'local · ' +
|
||||
(d.iso_count != null ? d.iso_count + ' isos' : 'never scanned')),
|
||||
d.last_error ? el('div', {class:'err'}, '⚠ ' + d.last_error) : null,
|
||||
d.last_hint ? el('div', {style:'margin-top:4px;opacity:.78;font-size:12px'}, d.last_hint) : null,
|
||||
]),
|
||||
el('button', {class:'ghost', onclick: async () => {
|
||||
const r = await postJSON('/api/local-dirs/' + encodeURIComponent(d.id) + '/scan', {});
|
||||
if (r.ok) render('storage');
|
||||
}}, 'Re-scan'),
|
||||
el('button', {class:'danger', onclick: async () => {
|
||||
if (!confirm('Remove ' + d.path + '? ISOs from this directory will disappear from the menu.')) return;
|
||||
await fetch('/api/local-dirs/' + encodeURIComponent(d.id), {method:'DELETE'});
|
||||
render('storage');
|
||||
}}, 'Remove'),
|
||||
el('span'),
|
||||
])) : [el('div', {class:'empty'}, 'No local directories registered.')];
|
||||
|
||||
const localDirCard = el('div', {class:'card'}, [
|
||||
el('header', {}, [
|
||||
el('h2', {}, 'Local directories'),
|
||||
el('span', {class:'sub'},
|
||||
localDirs.length + ' registered' +
|
||||
(!caps.available ? ' · recommended on this host' : '')),
|
||||
]),
|
||||
el('div', {class:'body'}, [
|
||||
el('p', {class:'msg', style:'margin-bottom:14px'},
|
||||
'Bind-mount a host directory into the container (' +
|
||||
'e.g. -v /mnt/user/isos:/mnt/external-isos), then enter the ' +
|
||||
'in-container path here. OpenPXE will scan it for ISOs and ' +
|
||||
'surface them next to uploaded and NFS-mounted images. No ' +
|
||||
'CAP_SYS_ADMIN required — this is the container-friendly ' +
|
||||
'workaround when kernel NFS mounts can\'t work.'),
|
||||
el('div', {class:'form-row'}, [
|
||||
el('label', {class:'field'}, [
|
||||
el('span', {class:'name'}, 'Path (inside container)'),
|
||||
ldPath,
|
||||
]),
|
||||
el('label', {class:'field'}, [
|
||||
el('span', {class:'name'}, 'Label (optional)'),
|
||||
ldLabel,
|
||||
]),
|
||||
]),
|
||||
addLocalDir, ldMsg,
|
||||
el('div', {style:'margin-top:18px;display:grid;gap:8px'}, ldRows),
|
||||
]),
|
||||
]);
|
||||
|
||||
// Disk-space card. Free + used + total for the volume hosting the
|
||||
// ISO directory, with a coloured bar. Warns at 80% and goes red at
|
||||
// 95% so the operator sees the runway shrinking before uploads
|
||||
@@ -843,24 +737,6 @@
|
||||
el('span', {class:'sub'}, mounts.length + ' configured'),
|
||||
]),
|
||||
el('div', {class:'body'}, [
|
||||
// v0.4.65: if the host kernel doesn't have NFS client
|
||||
// modules loaded (Unraid is the dominant case), in-
|
||||
// container mounts will fail regardless of capabilities or
|
||||
// privileged mode — there's nothing the operator can do
|
||||
// from inside the container. Surface this prominently and
|
||||
// point at the bind-mount workaround below.
|
||||
!caps.available
|
||||
? el('div', {class:'msg err', style:'margin-bottom:14px'}, [
|
||||
el('div', {}, [
|
||||
el('strong', {}, 'Host kernel has no NFS client support.'),
|
||||
document.createTextNode(' Mounting from this container will fail no matter what capabilities you grant it.'),
|
||||
]),
|
||||
el('div', {style:'margin-top:6px;opacity:.85;font-size:12px'},
|
||||
'Unraid is the most common case — its base kernel ships without the nfs/nfsv4 modules. ' +
|
||||
'Mount the share on the host (Unassigned Devices plugin, /etc/fstab, etc.), ' +
|
||||
'then bind-mount the resulting path into this container and register it as a Local directory below.'),
|
||||
])
|
||||
: null,
|
||||
el('div', {class:'form-row'}, [
|
||||
el('label', {class:'field'}, [
|
||||
el('span', {class:'name'}, 'NFS server'),
|
||||
@@ -881,14 +757,12 @@
|
||||
addNfs, nfsMsg,
|
||||
el('div', {style:'margin-top:18px;display:grid;gap:8px'}, nfsRows),
|
||||
el('p', {class:'msg', style:'margin-top:14px'},
|
||||
'Mounting NFS inside a container requires CAP_SYS_ADMIN, the ' +
|
||||
'mount.nfs binary (bundled in the default Docker image), and ' +
|
||||
'the host kernel having NFS client modules loaded. If any of ' +
|
||||
'these are missing, use Local directories below as a workaround.'),
|
||||
'Mounting NFS inside a container requires CAP_SYS_ADMIN and the ' +
|
||||
'mount.nfs binary (bundled in the default Docker image). On ' +
|
||||
'OpenShift, your SCC must allow CAP_SYS_ADMIN or you can run ' +
|
||||
'NFS mounts as a CSI driver outside the pod.'),
|
||||
]),
|
||||
]),
|
||||
// v0.4.65: Local directories — the bind-mount workaround.
|
||||
localDirCard,
|
||||
el('div', {class:'card'}, [
|
||||
el('header', {}, [
|
||||
el('h2', {}, 'Available images'),
|
||||
|
||||
Reference in New Issue
Block a user