feat: multi-distro support and tagged golden snapshots
Add Alpine, Debian, and Ubuntu rootfs support to `init [distro]`. Golden snapshots are now namespaced under `golden/<tag>/` so multiple baselines can coexist. `spawn [tag] [N]` selects which snapshot to clone from. Systemd-based distros (Debian, Ubuntu) get a fc-net-init systemd unit; Alpine keeps its inittab-based init. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,28 @@
|
||||
#spawn-btn:hover:not(:disabled) { background: #243e24; }
|
||||
#spawn-btn:disabled { opacity: .5; cursor: default; }
|
||||
|
||||
#spawn-controls {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#tag-select {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
color: #8be;
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
transition: border-color .15s, background .15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
#tag-select:hover { background: #222; }
|
||||
#tag-select:focus { border-color: #8be; }
|
||||
#tag-select:disabled { opacity: .5; cursor: default; }
|
||||
|
||||
#error-msg {
|
||||
color: #c44;
|
||||
font-size: .85rem;
|
||||
@@ -119,7 +141,10 @@
|
||||
<h1>fc-orch console</h1>
|
||||
<ul id="clone-list"></ul>
|
||||
<p class="none" id="no-clones" style="display:none">No running clones.</p>
|
||||
<button id="spawn-btn">+ Spawn clone</button>
|
||||
<div id="spawn-controls">
|
||||
<select id="tag-select"></select>
|
||||
<button id="spawn-btn">+ Spawn clone</button>
|
||||
</div>
|
||||
<p id="error-msg"></p>
|
||||
</div>
|
||||
|
||||
@@ -143,6 +168,7 @@
|
||||
const ul = document.getElementById('clone-list');
|
||||
const noneEl = document.getElementById('no-clones');
|
||||
const spawnBtn = document.getElementById('spawn-btn');
|
||||
const tagSelect = document.getElementById('tag-select');
|
||||
const errEl = document.getElementById('error-msg');
|
||||
|
||||
function showError(msg) {
|
||||
@@ -203,7 +229,7 @@
|
||||
fetch('/clones', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ net: true }),
|
||||
body: JSON.stringify({ net: true, tag: tagSelect.value }),
|
||||
})
|
||||
.then(r => {
|
||||
if (!r.ok) return r.text().then(t => { throw new Error(t); });
|
||||
@@ -219,7 +245,37 @@
|
||||
});
|
||||
});
|
||||
|
||||
function refreshTags() {
|
||||
fetch('/tags')
|
||||
.then(r => r.json())
|
||||
.then(tags => {
|
||||
tagSelect.innerHTML = '';
|
||||
if (!tags || tags.length === 0) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = 'No golden VMs';
|
||||
tagSelect.appendChild(opt);
|
||||
tagSelect.disabled = true;
|
||||
spawnBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
tagSelect.disabled = false;
|
||||
spawnBtn.disabled = false;
|
||||
tags.forEach(t => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = t;
|
||||
opt.textContent = t;
|
||||
if (t === 'default' || t === 'alpine') opt.selected = true;
|
||||
tagSelect.appendChild(opt);
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
console.error("fetch tags failed:", e);
|
||||
});
|
||||
}
|
||||
|
||||
refreshList();
|
||||
refreshTags();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user