Add auth stack conf vars and storage mode selection to setup wizard
host.conf.template: add AUTH STACK section (NPM/lldap/Authelia) so conf_upgrade.sh stops stripping those keys from host*.conf at 1am. setup wizard: storage mode selector auto-detects USB vs NVMe at first run; user can override; triggers storage_migrate.sh when mode differs from current SCRIPTS_DIR location. claude_startup.sh: reads HOST*_STORAGE_MODE_INTERNAL to pick internal or appdata path rather than always requiring array to be mounted.
This commit is contained in:
@@ -503,3 +503,27 @@
|
||||
HOSTN_SYS_WATCHDOG_CHECK_NETWORK=true
|
||||
HOSTN_SYS_WATCHDOG_CHECK_SSHD=true
|
||||
HOSTN_SYS_WATCHDOG_CHECK_RUNAWAY=false
|
||||
|
||||
# ==============================================================================================
|
||||
# ── AUTH STACK ────────────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
# Credentials for the Varaverk Auth Stack page (NPM, lldap, Authelia).
|
||||
|
||||
# ━━━ NginxProxyManager ━━━
|
||||
# Admin API runs on 7818 (not 81 — 81 is the partnership WebUI port).
|
||||
HOSTN_NPM_URL="http://localhost:7818"
|
||||
HOSTN_NPM_USER="" # NPM admin email
|
||||
HOSTN_NPM_PASS="" # NPM admin password
|
||||
|
||||
# ━━━ lldap ━━━
|
||||
HOSTN_LLDAP_URL="http://localhost:17170"
|
||||
HOSTN_LLDAP_USER="admin" # lldap admin username
|
||||
HOSTN_LLDAP_PASS="" # lldap admin password
|
||||
|
||||
# ━━━ Authelia ━━━
|
||||
HOSTN_AUTHELIA_CONFIG="/mnt/user/appdata/Authelia/configuration.yml"
|
||||
HOSTN_AUTHELIA_CONTAINER="Authelia"
|
||||
|
||||
# ==============================================================================================
|
||||
# ──────────────────────── End Of HOSTn Variables ──────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
|
||||
@@ -215,18 +215,25 @@ $hostId = strtoupper($mySlot);
|
||||
$hostIdLow = strtolower($mySlot);
|
||||
$confFile = $hostIdLow . '.conf';
|
||||
|
||||
// Storage mode: use wizard selection, fall back to auto-detect from boot transport
|
||||
$smParam = trim($_POST['storage_mode'] ?? '');
|
||||
if ($smParam === 'flash') {
|
||||
$storageInternal = 'false';
|
||||
} elseif ($smParam === 'internal') {
|
||||
$storageInternal = 'true';
|
||||
} else {
|
||||
$bootPart = trim(shell_exec('findmnt -n -o SOURCE /boot 2>/dev/null') ?: '');
|
||||
$bootDisk = $bootPart ? trim(shell_exec('lsblk -no pkname ' . escapeshellarg($bootPart) . ' 2>/dev/null') ?: '') : '';
|
||||
$transport = $bootDisk ? strtolower(trim(shell_exec('lsblk -dno TRAN /dev/' . escapeshellarg($bootDisk) . ' 2>/dev/null') ?: '')) : '';
|
||||
$storageInternal = ($transport !== 'usb') ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (!file_exists(CONF_DIR . '/' . $confFile)) {
|
||||
$template = @file_get_contents(CONF_DIR . '/host.conf.template') ?: '';
|
||||
if ($template) {
|
||||
$sshOwner = strtolower(preg_replace('/^unraid-/i', '', $myHostname));
|
||||
$sshKeyPath = '/root/.ssh/' . $sshOwner . '_rsync_automation';
|
||||
|
||||
// Auto-detect storage mode from boot device transport
|
||||
$bootPart = trim(shell_exec('findmnt -n -o SOURCE /boot 2>/dev/null') ?: '');
|
||||
$bootDisk = $bootPart ? trim(shell_exec('lsblk -no pkname ' . escapeshellarg($bootPart) . ' 2>/dev/null') ?: '') : '';
|
||||
$transport = $bootDisk ? strtolower(trim(shell_exec('lsblk -dno TRAN /dev/' . escapeshellarg($bootDisk) . ' 2>/dev/null') ?: '')) : '';
|
||||
$storageInternal = ($transport !== 'usb') ? 'true' : 'false';
|
||||
|
||||
$conf = str_replace('HOSTN', $hostId, $template);
|
||||
$conf = str_replace('hostn', $hostIdLow, $conf);
|
||||
$conf = preg_replace('/^(\s*' . $hostId . '_SSH_KEY\s*=\s*)""/m',
|
||||
@@ -251,9 +258,14 @@ if (file_exists($sshScript)) {
|
||||
// Auto-create Unraid API key and write into the fresh conf
|
||||
$apiKeyResult = vv_auto_create_api_key($hostId, $confFile);
|
||||
|
||||
$targetDir = ($storageInternal === 'true') ? '/boot/config/plugins/varaverk' : '/mnt/user/appdata/Varaverk';
|
||||
$needsMigration = (defined('SCRIPTS_DIR') && SCRIPTS_DIR !== $targetDir);
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'host_id' => $hostId,
|
||||
'api_key' => $apiKeyResult,
|
||||
'needs_migration'=> $needsMigration,
|
||||
'migrate_to' => $needsMigration ? ($storageInternal === 'true' ? 'internal' : 'flash') : null,
|
||||
'redirect' => '?tab=scheduler&vv_setup=master.conf',
|
||||
]);
|
||||
|
||||
@@ -86,6 +86,19 @@ hr.vv-hr { border: none; border-top: 1px solid #1e1e1e; margin: 22px 0; }
|
||||
|
||||
<div id="vv-detect-banner"><div class="loading">Detecting environment…</div></div>
|
||||
|
||||
<div class="vv-field">
|
||||
<label>Storage mode</label>
|
||||
<div class="vv-role-row" style="margin-bottom:4px">
|
||||
<div class="vv-role-btn" id="vv-store-flash" onclick="vvSetStorage('flash')">
|
||||
Appdata<br><span style="color:#555;font-size:10px;">USB boot · requires array</span>
|
||||
</div>
|
||||
<div class="vv-role-btn" id="vv-store-internal" onclick="vvSetStorage('internal')">
|
||||
Internal Boot<br><span style="color:#555;font-size:10px;">NVMe/SSD · no array dep</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="vv-store-hint" class="vv-hint"></div>
|
||||
</div>
|
||||
|
||||
<div class="vv-field">
|
||||
<label>This server's hostname</label>
|
||||
<input type="text" id="vv-hostname" value="<?= htmlspecialchars($detectedHostname) ?>" autocomplete="off" spellcheck="false">
|
||||
@@ -175,6 +188,18 @@ hr.vv-hr { border: none; border-top: 1px solid #1e1e1e; margin: 22px 0; }
|
||||
|
||||
<script>
|
||||
let _vvRedirect = '?tab=scheduler';
|
||||
let _vvStorageMode = 'flash';
|
||||
let _vvCurrentDir = '';
|
||||
|
||||
function vvSetStorage(mode) {
|
||||
_vvStorageMode = mode;
|
||||
document.getElementById('vv-store-flash')?.classList.toggle('active', mode === 'flash');
|
||||
document.getElementById('vv-store-internal')?.classList.toggle('active', mode === 'internal');
|
||||
const hint = document.getElementById('vv-store-hint');
|
||||
if (hint) hint.textContent = mode === 'flash'
|
||||
? 'Scripts live in appdata — requires array to be started. Recommended for USB flash boot.'
|
||||
: 'Scripts live on /boot — available before array mounts. Requires NVMe/SSD boot.';
|
||||
}
|
||||
|
||||
// ── Detection banner ──────────────────────────────────────────────────────────
|
||||
(function() {
|
||||
@@ -184,14 +209,12 @@ let _vvRedirect = '?tab=scheduler';
|
||||
.then(r => r.json()).then(d => {
|
||||
const b = document.getElementById('vv-detect-banner');
|
||||
if (!d.ok) { b.innerHTML = '<span style="color:#555">Detection unavailable</span>'; return; }
|
||||
const modeLabel = d.mode === 'internal'
|
||||
? '<span style="color:#4a8">internal (NVMe/SSD)</span>'
|
||||
: '<span style="color:#a84">flash mode (USB boot)</span>';
|
||||
_vvCurrentDir = d.scripts_dir || '';
|
||||
b.innerHTML =
|
||||
'<div class="vv-det-row"><span class="vv-det-lbl">OS</span><span class="vv-det-val">Unraid ' + (d.unraid_ver||'') + '</span></div>' +
|
||||
'<div class="vv-det-row"><span class="vv-det-lbl">Boot device</span><span class="vv-det-val">' + d.boot_device + ' (' + d.transport + ')</span></div>' +
|
||||
'<div class="vv-det-row"><span class="vv-det-lbl">Storage mode</span><span class="vv-det-val">' + modeLabel + '</span></div>' +
|
||||
'<div class="vv-det-row"><span class="vv-det-lbl">Scripts dir</span><span class="vv-det-val" style="color:#666">' + d.scripts_dir + '</span></div>';
|
||||
vvSetStorage(d.mode);
|
||||
const hf = document.getElementById('vv-hostname');
|
||||
if (hf && !hf.value.trim()) hf.value = d.hostname;
|
||||
}).catch(() => {
|
||||
@@ -377,10 +400,34 @@ function vvDoSave() {
|
||||
vvSetBtn('Saving…', true);
|
||||
fetch('/plugins/varaverk/api/setup.php', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({action:'save', host1, host2, my_slot:mySlot, my_hostname:hostname})
|
||||
body: new URLSearchParams({action:'save', host1, host2, my_slot:mySlot, my_hostname:hostname, storage_mode:_vvStorageMode})
|
||||
}).then(r => r.json()).then(d => {
|
||||
if (d.ok) { vvShowStep2(d.redirect || '?tab=scheduler', d.api_key); }
|
||||
else { vvSetBtn('Save and continue →', false); vvSetStatus('✗ ' + (d.error||'Error'), 'err'); }
|
||||
if (d.ok) {
|
||||
if (d.needs_migration) {
|
||||
const dest = d.migrate_to === 'flash' ? 'appdata' : '/boot';
|
||||
vvSetStatus('⟳ Migrating scripts to ' + dest + '…', '');
|
||||
vvDoMigration(d.migrate_to, d.redirect || '?tab=scheduler', d.api_key);
|
||||
} else {
|
||||
vvShowStep2(d.redirect || '?tab=scheduler', d.api_key);
|
||||
}
|
||||
} else { vvSetBtn('Save and continue →', false); vvSetStatus('✗ ' + (d.error||'Error'), 'err'); }
|
||||
}).catch(() => { vvSetBtn('Save and continue →', false); vvSetStatus('✗ Request failed', 'err'); });
|
||||
}
|
||||
|
||||
function vvDoMigration(to, redirect, apiKey) {
|
||||
fetch('/plugins/varaverk/api/storage.php', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({action: 'migrate', to})
|
||||
}).then(r => r.json()).then(d => {
|
||||
if (d.ok) {
|
||||
vvShowStep2(redirect, apiKey);
|
||||
} else {
|
||||
vvSetBtn('Save and continue →', false);
|
||||
vvSetStatus('✗ Migration failed — ' + (d.error || 'check install.log'), 'err');
|
||||
}
|
||||
}).catch(() => {
|
||||
vvSetBtn('Save and continue →', false);
|
||||
vvSetStatus('✗ Migration request failed', 'err');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
+54
-22
@@ -5,18 +5,27 @@
|
||||
#
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Restores Claude Code's persistent data after an Unraid reboot and launches Claude.
|
||||
# Unraid's root filesystem lives in RAM — /root/.claude and /root/.local are wiped on
|
||||
# every reboot. This script symlinks both directories back to persistent appdata storage
|
||||
# before launching Claude, so memory, sessions, and settings survive across reboots.
|
||||
# Restores Claude Code's persistent data after an Unraid reboot.
|
||||
# /root is RAM — wiped on every boot. This script re-creates symlinks so
|
||||
# Claude's memory, sessions, settings, and binary survive across reboots.
|
||||
#
|
||||
# On first run with no existing persistent data, migrates from the current live locations:
|
||||
# /root/.claude → PERSIST_DIR/.claude (memory, sessions, settings)
|
||||
# /root/.local/share/claude → PERSIST_DIR/local/share/claude (installed binaries)
|
||||
# Subsequent runs skip the migration and only create the symlinks.
|
||||
# Storage mode is read from the host conf file:
|
||||
#
|
||||
# Standalone script — no common.sh dependency. Safe to run directly from terminal
|
||||
# or from array_started.sh.
|
||||
# HOST*_STORAGE_MODE_INTERNAL=true → Internal (boot) mode
|
||||
# .claude data → /boot/config/claude
|
||||
# binary → /boot/config/claude-bin
|
||||
# No array dependency — runs even before array mounts.
|
||||
#
|
||||
# HOST*_STORAGE_MODE_INTERNAL=false → Appdata mode
|
||||
# .claude data → /mnt/user/appdata/claude-code/.claude
|
||||
# binary → /mnt/user/appdata/claude-code/local/share/claude
|
||||
# Requires array to be mounted.
|
||||
#
|
||||
# On first run in either mode, migrates any existing live data to persistent
|
||||
# storage. Subsequent runs only re-create the symlinks.
|
||||
#
|
||||
# Standalone script — no common.sh dependency. Safe to run directly from
|
||||
# terminal or from array_started.sh.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
@@ -30,14 +39,9 @@
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
PERSIST_DIR="/mnt/user/appdata/claude-code"
|
||||
CLAUDE_DATA="$PERSIST_DIR/.claude"
|
||||
CLAUDE_BIN="$PERSIST_DIR/local/share/claude"
|
||||
|
||||
LAUNCH=false
|
||||
[[ "$1" == "--launch" ]] && LAUNCH=true
|
||||
|
||||
# Standalone — no common.sh dependency
|
||||
_log() { echo " ✅ $*"; }
|
||||
_warn() { echo " ⚠️ $*"; }
|
||||
_err() { echo " ❌ $*" >&2; }
|
||||
@@ -46,15 +50,48 @@ echo ""
|
||||
echo "━━━ Claude Code Startup ━━━"
|
||||
echo ""
|
||||
|
||||
# ── Array must be mounted ─────────────────────────────────────────────────────────────────────
|
||||
# ── Detect storage mode ───────────────────────────────────────────────────────────────────────
|
||||
CONF_DIR="/boot/config/plugins/varaverk/Configurations"
|
||||
STORAGE_INTERNAL=false
|
||||
for _conf in "$CONF_DIR"/host*.conf; do
|
||||
[[ -f "$_conf" ]] || continue
|
||||
if grep -q "_STORAGE_MODE_INTERNAL=true" "$_conf" 2>/dev/null; then
|
||||
STORAGE_INTERNAL=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$STORAGE_INTERNAL" == true ]]; then
|
||||
CLAUDE_DATA="/boot/config/plugins/varaverk/claude-data"
|
||||
CLAUDE_BIN="/boot/config/plugins/varaverk/claude-bin"
|
||||
_log "Storage mode: internal boot"
|
||||
else
|
||||
PERSIST_DIR="/mnt/user/appdata/claude-code"
|
||||
CLAUDE_DATA="$PERSIST_DIR/.claude"
|
||||
CLAUDE_BIN="$PERSIST_DIR/local/share/claude"
|
||||
_log "Storage mode: appdata"
|
||||
fi
|
||||
|
||||
# ── Array check (appdata mode only) ──────────────────────────────────────────────────────────
|
||||
if [[ "$STORAGE_INTERNAL" == false ]]; then
|
||||
if ! mountpoint -q /mnt/user 2>/dev/null; then
|
||||
_err "Array not mounted — /mnt/user not available"
|
||||
_err "Array not mounted — /mnt/user not available (required for appdata mode)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Create persistent dirs ────────────────────────────────────────────────────────────────────
|
||||
mkdir -p "$CLAUDE_DATA" "$CLAUDE_BIN"
|
||||
|
||||
# ── Migrate from old /boot/config/claude location (internal mode only) ───────────────────────
|
||||
if [[ "$STORAGE_INTERNAL" == true && -d /boot/config/claude && "$CLAUDE_DATA" != "/boot/config/claude" ]]; then
|
||||
if [[ -z "$(ls -A "$CLAUDE_DATA" 2>/dev/null)" ]]; then
|
||||
_warn "Migrating old /boot/config/claude → $CLAUDE_DATA"
|
||||
cp -a /boot/config/claude/. "$CLAUDE_DATA/"
|
||||
_log "Migrated .claude data from old location"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Migrate .claude on first run ──────────────────────────────────────────────────────────────
|
||||
if [[ ! -L /root/.claude && -d /root/.claude ]]; then
|
||||
_warn "First run — migrating /root/.claude → $CLAUDE_DATA"
|
||||
@@ -75,8 +112,6 @@ if [[ ! -L /root/.local/share/claude && -d /root/.local/share/claude ]]; then
|
||||
fi
|
||||
|
||||
# ── Create symlinks ───────────────────────────────────────────────────────────────────────────
|
||||
# Remove any real directories first — ln -sfn silently creates inside a dir instead of
|
||||
# replacing it, which produces a circular symlink on subsequent runs after migration.
|
||||
mkdir -p /root/.local/share /root/.local/bin
|
||||
|
||||
[[ -d /root/.claude && ! -L /root/.claude ]] && rm -rf /root/.claude
|
||||
@@ -88,8 +123,6 @@ ln -sfn "$CLAUDE_BIN" /root/.local/share/claude
|
||||
_log "claude binary → $CLAUDE_BIN"
|
||||
|
||||
# ── Symlink CLAUDE.md ─────────────────────────────────────────────────────────────────────────
|
||||
# Lives on /boot so it survives reboots without appdata. Symlinked into /root so Claude
|
||||
# picks it up automatically from the working directory on every session.
|
||||
CLAUDE_MD="/boot/config/plugins/varaverk/CLAUDE.md"
|
||||
if [[ -f "$CLAUDE_MD" ]]; then
|
||||
ln -sfn "$CLAUDE_MD" /root/CLAUDE.md
|
||||
@@ -111,7 +144,6 @@ _log "claude v$LATEST ready"
|
||||
|
||||
echo ""
|
||||
|
||||
# ── Setup-only mode (used by array_started.sh or other callers) ─────────────────────────────────
|
||||
if [[ "$LAUNCH" == false ]]; then
|
||||
_log "Setup complete — run 'claude' to start"
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user