Compare commits
15 Commits
1c2df46ac4
...
staging-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8413093d43 | ||
|
|
1a8a1736bf | ||
|
|
51c7d172b3 | ||
|
|
6999ae5680 | ||
|
|
0c3f74e7de | ||
|
|
d2703ff84b | ||
|
|
1a9e0825fc | ||
|
|
284a861927 | ||
|
|
02b4e6b5b4 | ||
|
|
60084c292e | ||
|
|
fa22a080b9 | ||
|
|
70f0af98f6 | ||
|
|
cd4df316ae | ||
|
|
ff55dce746 | ||
|
|
5a86c03f74 |
@@ -2162,6 +2162,7 @@ async def api_service_detail(unit: str, icon: str | None = None):
|
||||
"credentials": resolved_creds,
|
||||
"needs_domain": needs_domain,
|
||||
"domain": domain,
|
||||
"domain_name": domain_key,
|
||||
"domain_status": domain_status,
|
||||
"port_requirements": port_requirements,
|
||||
"port_statuses": port_statuses,
|
||||
|
||||
@@ -146,17 +146,6 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.onboarding-card--scroll {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border-color) transparent;
|
||||
}
|
||||
|
||||
.onboarding-card--ports {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Body text */
|
||||
|
||||
.onboarding-body-text {
|
||||
@@ -228,7 +217,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 4px;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.onboarding-btn-next {
|
||||
|
||||
@@ -310,6 +310,12 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ── Service detail: Domain configure button ─────────────────────── */
|
||||
|
||||
.svc-detail-domain-btn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* ── Service detail: Addon feature toggle ────────────────────────── */
|
||||
|
||||
.svc-detail-addon-row {
|
||||
|
||||
@@ -244,8 +244,8 @@ async function openServiceDetailModal(unit, name, icon) {
|
||||
'<li>Find the domain you purchased for this service</li>' +
|
||||
'<li>Create a Dynamic DNS record pointing to your external IP: <code>' + escHtml(ds.expected_ip || "—") + '</code></li>' +
|
||||
'<li>Copy the DDNS curl command from Njal.la\'s dashboard</li>' +
|
||||
'<li>You can re-enter it in the Feature Manager to update your configuration</li>' +
|
||||
'</ol>' +
|
||||
'<button class="btn btn-primary svc-detail-domain-btn" id="svc-detail-reconfig-domain-btn">🔄 Reconfigure Domain</button>' +
|
||||
'</div>';
|
||||
} else {
|
||||
domainBadge = '<span class="svc-detail-domain-value">' + escHtml(data.domain) + '</span>';
|
||||
@@ -257,9 +257,9 @@ async function openServiceDetailModal(unit, name, icon) {
|
||||
'<p style="margin-top:8px">To get this service working:</p>' +
|
||||
'<ol>' +
|
||||
'<li>Purchase a subdomain at <a href="https://njal.la" target="_blank">njal.la</a> (if you haven\'t already)</li>' +
|
||||
'<li>Go to the <strong>Feature Manager</strong> in the sidebar</li>' +
|
||||
'<li>Find this service and configure your domain through the setup wizard</li>' +
|
||||
'<li>Use the button below to configure your domain through the setup wizard</li>' +
|
||||
'</ol>' +
|
||||
'<button class="btn btn-primary svc-detail-domain-btn" id="svc-detail-config-domain-btn">🌐 Configure Domain</button>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
@@ -385,6 +385,26 @@ async function openServiceDetailModal(unit, name, icon) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Configure Domain button (for non-feature services that need a domain)
|
||||
var configDomainBtn = document.getElementById("svc-detail-config-domain-btn");
|
||||
var reconfigDomainBtn = document.getElementById("svc-detail-reconfig-domain-btn");
|
||||
var domainBtn = configDomainBtn || reconfigDomainBtn;
|
||||
if (domainBtn && data.needs_domain && data.domain_name) {
|
||||
var pseudoFeat = {
|
||||
id: data.domain_name,
|
||||
name: name,
|
||||
domain_name: data.domain_name,
|
||||
needs_ddns: true,
|
||||
extra_fields: []
|
||||
};
|
||||
domainBtn.addEventListener("click", function() {
|
||||
closeCredsModal();
|
||||
openDomainSetupModal(pseudoFeat, function() {
|
||||
openServiceDetailModal(unit, name, icon);
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if ($credsBody) $credsBody.innerHTML = '<p class="creds-empty">Could not load service details.</p>';
|
||||
}
|
||||
|
||||
@@ -308,6 +308,8 @@ async function loadStep3() {
|
||||
html += '<label class="onboarding-domain-label onboarding-domain-label--sub">Njal.la DDNS Curl Command</label>';
|
||||
html += '<input class="onboarding-domain-input domain-field-input" type="text" id="ddns-input-' + escHtml(d.name) + '" data-ddns="' + escHtml(d.name) + '" placeholder="curl "https://njal.la/update/?h=' + escHtml(d.name) + '.yourdomain.com&k=abc123&auto"" />';
|
||||
html += '<p class="onboarding-hint" style="margin-top:4px;">ℹ Paste the curl URL from your Njal.la dashboard\'s Dynamic record</p>';
|
||||
html += '<button type="button" class="btn btn-primary onboarding-domain-save-btn" data-save-domain="' + escHtml(d.name) + '" style="align-self:flex-start;margin-top:8px;font-size:0.82rem;padding:6px 16px;">Save</button>';
|
||||
html += '<span class="onboarding-domain-save-status" id="domain-save-status-' + escHtml(d.name) + '" style="font-size:0.82rem;min-height:1.2em;"></span>';
|
||||
html += '</div>';
|
||||
});
|
||||
}
|
||||
@@ -318,9 +320,78 @@ async function loadStep3() {
|
||||
html += '<label class="onboarding-domain-label">📧 SSL Certificate Email</label>';
|
||||
html += '<p class="onboarding-hint onboarding-hint--inline">Let\'s Encrypt uses this for certificate expiry notifications.</p>';
|
||||
html += '<input class="onboarding-domain-input domain-field-input" type="email" id="ssl-email-input" placeholder="you@example.com" value="' + escHtml(emailVal) + '" />';
|
||||
html += '<button type="button" class="btn btn-primary onboarding-domain-save-btn" data-save-email="true" style="align-self:flex-start;margin-top:8px;font-size:0.82rem;padding:6px 16px;">Save</button>';
|
||||
html += '<span class="onboarding-domain-save-status" id="domain-save-status-email" style="font-size:0.82rem;min-height:1.2em;"></span>';
|
||||
html += '</div>';
|
||||
|
||||
body.innerHTML = html;
|
||||
|
||||
// Wire per-field save buttons for domains
|
||||
body.querySelectorAll('[data-save-domain]').forEach(function(btn) {
|
||||
btn.addEventListener('click', async function() {
|
||||
var domainName = btn.dataset.saveDomain;
|
||||
var domainInput = document.getElementById('domain-input-' + domainName);
|
||||
var ddnsInput = document.getElementById('ddns-input-' + domainName);
|
||||
var statusEl = document.getElementById('domain-save-status-' + domainName);
|
||||
var domainVal = domainInput ? domainInput.value.trim() : '';
|
||||
var ddnsVal = ddnsInput ? ddnsInput.value.trim() : '';
|
||||
|
||||
if (!domainVal) {
|
||||
if (statusEl) { statusEl.textContent = '⚠ Enter a domain first'; statusEl.style.color = 'var(--red)'; }
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Saving…';
|
||||
if (statusEl) { statusEl.textContent = ''; }
|
||||
|
||||
try {
|
||||
await apiFetch('/api/domains/set', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ domain_name: domainName, domain: domainVal, ddns_url: ddnsVal }),
|
||||
});
|
||||
if (statusEl) { statusEl.textContent = '✓ Saved'; statusEl.style.color = 'var(--green)'; }
|
||||
} catch (err) {
|
||||
if (statusEl) { statusEl.textContent = '⚠ ' + err.message; statusEl.style.color = 'var(--red)'; }
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Save';
|
||||
});
|
||||
});
|
||||
|
||||
// Wire save button for SSL email
|
||||
body.querySelectorAll('[data-save-email]').forEach(function(btn) {
|
||||
btn.addEventListener('click', async function() {
|
||||
var emailInput = document.getElementById('ssl-email-input');
|
||||
var statusEl = document.getElementById('domain-save-status-email');
|
||||
var emailVal = emailInput ? emailInput.value.trim() : '';
|
||||
|
||||
if (!emailVal) {
|
||||
if (statusEl) { statusEl.textContent = '⚠ Enter an email first'; statusEl.style.color = 'var(--red)'; }
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Saving…';
|
||||
if (statusEl) { statusEl.textContent = ''; }
|
||||
|
||||
try {
|
||||
await apiFetch('/api/domains/set-email', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email: emailVal }),
|
||||
});
|
||||
if (statusEl) { statusEl.textContent = '✓ Saved'; statusEl.style.color = 'var(--green)'; }
|
||||
} catch (err) {
|
||||
if (statusEl) { statusEl.textContent = '⚠ ' + err.message; statusEl.style.color = 'var(--red)'; }
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Save';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function saveStep3() {
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<span class="onboarding-step-icon">🔒</span>
|
||||
<h2 class="onboarding-step-title">Create Your Password</h2>
|
||||
<p class="onboarding-step-desc">
|
||||
Choose a strong password for your <strong>'free'</strong> user account. This will be your login password for the desktop, SSH, and the Hub.
|
||||
Choose a strong password for your <strong>'free'</strong> user account.
|
||||
</p>
|
||||
</div>
|
||||
<div class="onboarding-card" id="step-2-body">
|
||||
@@ -105,7 +105,7 @@
|
||||
Finally, paste the DDNS curl command from your Njal.la dashboard for each service below.
|
||||
</p>
|
||||
</div>
|
||||
<div class="onboarding-card onboarding-card--scroll" id="step-3-body">
|
||||
<div class="onboarding-card" id="step-3-body">
|
||||
<p class="onboarding-loading">Loading service information…</p>
|
||||
</div>
|
||||
<div id="step-3-status" class="onboarding-save-status"></div>
|
||||
@@ -127,7 +127,7 @@
|
||||
<strong>Ports 80 and 443 must be open for SSL certificates to work.</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="onboarding-card onboarding-card--ports" id="step-4-body">
|
||||
<div class="onboarding-card" id="step-4-body">
|
||||
<p class="onboarding-loading">Checking ports…</p>
|
||||
</div>
|
||||
<div class="onboarding-footer">
|
||||
|
||||
@@ -966,7 +966,9 @@ class InstallerWindow(Adw.ApplicationWindow):
|
||||
if proc.returncode != 0:
|
||||
log(proc.stderr)
|
||||
raise RuntimeError(proc.stderr.strip() or "Failed to write deployed flake.nix")
|
||||
run(["sudo", "rm", "-f", "/mnt/etc/nixos/flake.lock"])
|
||||
GLib.idle_add(append_text, buf, "Locking flake to staging-dev...\n")
|
||||
run_stream(["sudo", "nix", "--extra-experimental-features", "nix-command flakes",
|
||||
"flake", "lock", "/mnt/etc/nixos"], buf)
|
||||
|
||||
GLib.idle_add(self.push_complete)
|
||||
|
||||
|
||||
@@ -70,6 +70,16 @@ lib.mkIf config.sovran_systemsOS.services.bitcoin {
|
||||
|
||||
nix-bitcoin.useVersionLockedPkgs = false;
|
||||
|
||||
systemd.services.bitcoind = {
|
||||
requires = [ "run-media-Second_Drive.mount" ];
|
||||
after = [ "run-media-Second_Drive.mount" ];
|
||||
};
|
||||
|
||||
systemd.services.electrs = {
|
||||
requires = [ "run-media-Second_Drive.mount" ];
|
||||
after = [ "run-media-Second_Drive.mount" ];
|
||||
};
|
||||
|
||||
systemd.services.sovran-btc-permissions = {
|
||||
description = "Fix Bitcoin/Electrs data directory ownership on second drive";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
Reference in New Issue
Block a user