From f2a808ed13020e73959daf534dae793ac72996e1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:29:46 +0000 Subject: [PATCH 1/2] Initial plan From d28f224ad538fe819dc7a4f71d3a37786830cb8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:36:59 +0000 Subject: [PATCH 2/2] feat: add password creation step to onboarding wizard (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add GET /api/security/password-is-default endpoint in server.py - Add Step 2 (Create Your Password) to onboarding wizard HTML - Renumber old steps: Domains→3, Ports→4, Complete→5 - Add 5th step dot indicator - Update onboarding.js: TOTAL_STEPS=5, ROLE_SKIP_STEPS=[3,4] for desktop/node - Add loadStep2/saveStep2 for password step with smart default detection - Rename old step functions to loadStep3/saveStep3/loadStep4 - Add password form CSS styles in onboarding.css Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/74a30916-fb2d-4f1d-9763-e380b1aa5540 Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- app/sovran_systemsos_web/server.py | 11 + .../static/css/onboarding.css | 104 ++++++++++ app/sovran_systemsos_web/static/onboarding.js | 191 +++++++++++++++--- .../templates/onboarding.html | 52 +++-- 4 files changed, 319 insertions(+), 39 deletions(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index b6b620b..165aedc 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -2953,6 +2953,17 @@ async def api_security_status(): return {"status": status, "warning": warning} +@app.get("/api/security/password-is-default") +async def api_password_is_default(): + """Check if the free account password is still the factory default.""" + try: + with open("/var/lib/secrets/free-password", "r") as f: + current = f.read().strip() + return {"is_default": current == "free"} + except FileNotFoundError: + return {"is_default": True} + + # ── System password change ──────────────────────────────────────── FREE_PASSWORD_FILE = "/var/lib/secrets/free-password" diff --git a/app/sovran_systemsos_web/static/css/onboarding.css b/app/sovran_systemsos_web/static/css/onboarding.css index 42343bb..c250f39 100644 --- a/app/sovran_systemsos_web/static/css/onboarding.css +++ b/app/sovran_systemsos_web/static/css/onboarding.css @@ -575,6 +575,110 @@ color: var(--text-secondary); } +/* ── Password step (Step 2) ─────────────────────────────────────── */ + +.onboarding-password-group { + display: flex; + flex-direction: column; + gap: 6px; + padding: 10px 0; +} + +.onboarding-password-input-wrap { + display: flex; + align-items: center; + gap: 6px; +} + +.onboarding-password-input { + flex: 1; + padding: 9px 12px; + border: 1px solid var(--border-color); + border-radius: var(--radius-btn); + background-color: var(--card-color); + color: var(--text-primary); + font-size: 0.88rem; + font-family: 'Cantarell', 'Inter', 'Segoe UI', sans-serif; + transition: border-color 0.15s; +} + +.onboarding-password-input:focus { + outline: none; + border-color: var(--accent-color); +} + +.onboarding-password-toggle { + padding: 6px 10px; + background-color: var(--card-color); + border: 1px solid var(--border-color); + border-radius: var(--radius-btn); + color: var(--text-secondary); + cursor: pointer; + font-size: 1rem; + line-height: 1; + transition: background-color 0.15s, border-color 0.15s; + flex-shrink: 0; +} + +.onboarding-password-toggle:hover { + background-color: rgba(137, 180, 250, 0.12); + border-color: var(--accent-color); +} + +.onboarding-password-hint { + font-size: 0.78rem; + color: var(--text-dim); + line-height: 1.4; +} + +.onboarding-password-warning { + padding: 10px 14px; + background-color: rgba(229, 165, 10, 0.1); + border: 1px solid rgba(229, 165, 10, 0.35); + border-radius: 8px; + font-size: 0.85rem; + color: var(--yellow); + line-height: 1.5; + margin-top: 6px; +} + +.onboarding-password-success { + padding: 12px 16px; + background-color: rgba(166, 227, 161, 0.1); + border: 1px solid rgba(166, 227, 161, 0.35); + border-radius: 8px; + font-size: 0.92rem; + color: var(--green); + line-height: 1.5; +} + +.onboarding-password-optional { + margin-top: 12px; + font-size: 0.88rem; +} + +.onboarding-password-optional > summary { + cursor: pointer; + font-size: 0.85rem; + font-weight: 600; + color: var(--accent-color); + list-style: none; + user-select: none; +} + +.onboarding-password-optional > summary::-webkit-details-marker { + display: none; +} + +.onboarding-password-optional > summary::before { + content: '▶ '; + font-size: 0.65em; +} + +.onboarding-password-optional[open] > summary::before { + content: '▼ '; +} + /* ── Reboot overlay ─────────────────────────────────────────────── */ .reboot-overlay { diff --git a/app/sovran_systemsos_web/static/onboarding.js b/app/sovran_systemsos_web/static/onboarding.js index 55a7499..4821262 100644 --- a/app/sovran_systemsos_web/static/onboarding.js +++ b/app/sovran_systemsos_web/static/onboarding.js @@ -1,21 +1,25 @@ /* Sovran_SystemsOS Hub — First-Boot Onboarding Wizard - Drives the 4-step post-install setup flow. */ + Drives the 5-step post-install setup flow. */ "use strict"; // ── Constants ───────────────────────────────────────────────────── -const TOTAL_STEPS = 4; +const TOTAL_STEPS = 5; -// Steps to skip per role (steps 2 and 3 involve domain/port setup) +// Steps to skip per role (steps 3 and 4 involve domain/port setup) +// Step 2 (password) is NEVER skipped — all roles need it. const ROLE_SKIP_STEPS = { - "desktop": [2, 3], - "node": [2, 3], + "desktop": [3, 4], + "node": [3, 4], }; // ── Role state (loaded at init) ─────────────────────────────────── var _onboardingRole = "server_plus_desktop"; +// Password default state (loaded at step 2) +var _passwordIsDefault = true; + // Domains that may need configuration, with service unit mapping for enabled check const DOMAIN_DEFS = [ { name: "matrix", label: "Matrix (Synapse)", unit: "matrix-synapse.service", needsDdns: true }, @@ -91,6 +95,8 @@ function showStep(step) { // Lazy-load step content if (step === 2) loadStep2(); if (step === 3) loadStep3(); + if (step === 4) loadStep4(); + // Step 5 (Complete) is static — no lazy-load needed } // Return the next step number, skipping over role-excluded steps @@ -119,12 +125,135 @@ async function loadStep1() { } catch (_) {} } -// ── Step 2: Domain Configuration ───────────────────────────────── +// ── Step 2: Create Your Password ───────────────────────────────── async function loadStep2() { var body = document.getElementById("step-2-body"); if (!body) return; + var nextBtn = document.getElementById("step-2-next"); + + try { + var result = await apiFetch("/api/security/password-is-default"); + _passwordIsDefault = result.is_default !== false; + } catch (_) { + _passwordIsDefault = true; + } + + if (_passwordIsDefault) { + // Factory-sealed scenario: password must be set before continuing + if (nextBtn) nextBtn.textContent = "Set Password & Continue \u2192"; + + body.innerHTML = + '
Minimum 8 characters
' + + 'Minimum 8 characters
' + + 'Checking ports…
'; @@ -327,10 +456,10 @@ async function loadStep3() { body.innerHTML = html; } -// ── Step 4: Complete ────────────────────────────────────────────── +// ── Step 5: Complete ────────────────────────────────────────────── async function completeOnboarding() { - var btn = document.getElementById("step-4-finish"); + var btn = document.getElementById("step-5-finish"); if (btn) { btn.disabled = true; btn.textContent = "Finishing…"; } try { @@ -345,28 +474,40 @@ async function completeOnboarding() { // ── Event wiring ────────────────────────────────────────────────── function wireNavButtons() { - // Step 1 → next (may skip 2+3 for desktop/node) + // Step 1 → next var s1next = document.getElementById("step-1-next"); if (s1next) s1next.addEventListener("click", function() { showStep(nextStep(1)); }); - // Step 2 → 3 (save first) + // Step 2 → 3 (save password first) var s2next = document.getElementById("step-2-next"); if (s2next) s2next.addEventListener("click", async function() { s2next.disabled = true; + var origText = s2next.textContent; s2next.textContent = "Saving…"; - await saveStep2(); + var ok = await saveStep2(); s2next.disabled = false; - s2next.textContent = "Save & Continue →"; - showStep(nextStep(2)); + s2next.textContent = origText; + if (ok) showStep(nextStep(2)); }); - // Step 3 → 4 (Complete) + // Step 3 → 4 (save domains first) var s3next = document.getElementById("step-3-next"); - if (s3next) s3next.addEventListener("click", function() { showStep(nextStep(3)); }); + if (s3next) s3next.addEventListener("click", async function() { + s3next.disabled = true; + s3next.textContent = "Saving…"; + await saveStep3(); + s3next.disabled = false; + s3next.textContent = "Save & Continue →"; + showStep(nextStep(3)); + }); - // Step 4: finish - var s4finish = document.getElementById("step-4-finish"); - if (s4finish) s4finish.addEventListener("click", completeOnboarding); + // Step 4 → 5 (Complete) + var s4next = document.getElementById("step-4-next"); + if (s4next) s4next.addEventListener("click", function() { showStep(nextStep(4)); }); + + // Step 5: finish + var s5finish = document.getElementById("step-5-finish"); + if (s5finish) s5finish.addEventListener("click", completeOnboarding); // Back buttons document.querySelectorAll(".onboarding-btn-back").forEach(function(btn) { diff --git a/app/sovran_systemsos_web/templates/onboarding.html b/app/sovran_systemsos_web/templates/onboarding.html index cd701a8..edc15d8 100644 --- a/app/sovran_systemsos_web/templates/onboarding.html +++ b/app/sovran_systemsos_web/templates/onboarding.html @@ -34,6 +34,8 @@ 3 4 + + 5 @@ -70,8 +72,29 @@ - + + + +