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:
Gmer4Lfe
2026-06-13 13:05:46 -04:00
parent 60ff658181
commit ba1259c05c
4 changed files with 157 additions and 42 deletions
+24
View File
@@ -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 ──────────────────────────────────────────────
# ==============================================================================================
+18 -6
View File
@@ -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',
]);
+54 -7
View File
@@ -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>
+55 -23
View File
@@ -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 ─────────────────────────────────────────────────────────────────────
if ! mountpoint -q /mnt/user 2>/dev/null; then
_err "Array not mounted — /mnt/user not available"
# ── 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 (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 ""