(function () { var wizard = document.getElementById('archivebox-setup-wizard'); if (!wizard) return; var baseUrlInput = document.getElementById('archivebox-setup-base-url'); var securityModeInput = document.getElementById('archivebox-setup-security-mode'); var publicIndexInput = document.getElementById('archivebox-setup-public-index'); var publicAddInput = document.getElementById('archivebox-setup-public-add'); var permissionsInput = document.getElementById('archivebox-setup-permissions'); var hostingInputs = document.querySelectorAll('input[name="archivebox-hosting-location"]'); var dnsInputs = document.querySelectorAll('input[name="archivebox-dns-mode"]'); var tlsInputs = document.querySelectorAll('input[name="archivebox-tls-mode"]'); var reviewButton = document.getElementById('archivebox-setup-review'); var validationStatus = document.getElementById('archivebox-setup-validation'); var probeTimer = null; var probeGeneration = 0; var currentPreview = null; var machineAdminUrl = new URL(wizard.dataset.machineAdminUrl, window.location.origin); if (window.location.pathname === machineAdminUrl.pathname && new URLSearchParams(window.location.search).has('BASE_URL')) { wizard.remove(); return; } publicIndexInput.checked = wizard.dataset.publicIndex === 'true'; publicAddInput.checked = wizard.dataset.publicAddView === 'true'; permissionsInput.value = wizard.dataset.permissions || 'public'; function selectedValue(inputs) { var selected = Array.prototype.find.call(inputs, function(input) { return input.checked; }); return selected ? selected.value : ''; } function selectValue(inputs, value) { Array.prototype.forEach.call(inputs, function(input) { input.checked = input.value === value; }); } function initializeQuestionSections() { document.querySelectorAll('.abx-question[data-status-id]').forEach(function(question) { var status = document.getElementById(question.dataset.statusId); var toggle = question.querySelector('.abx-question-toggle'); var toggleIcon = question.querySelector('.abx-question-toggle-icon'); function setCollapsed(collapsed) { question.classList.toggle('is-collapsed', collapsed); toggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); toggleIcon.textContent = collapsed ? '▸' : '▾'; } function syncQuestionState() { var statusText = status.textContent.trim(); var isValid = statusText.indexOf('✅') === 0; var isInvalid = statusText.indexOf('❌') === 0; question.classList.toggle('is-valid', isValid); question.classList.toggle('is-invalid', isInvalid); if (isValid) setCollapsed(true); if (isInvalid) setCollapsed(false); } toggle.addEventListener('click', function() { setCollapsed(!question.classList.contains('is-collapsed')); }); new MutationObserver(syncQuestionState).observe(status, {childList: true, characterData: true, subtree: true}); syncQuestionState(); }); } initializeQuestionSections(); var detectedUrl = new URL(baseUrlInput.value); var detectedLocalhost = detectedUrl.hostname === 'localhost' || detectedUrl.hostname.endsWith('.localhost'); if (detectedLocalhost) { selectValue(hostingInputs, 'localhost'); selectValue(dnsInputs, 'localhost'); selectValue(tlsInputs, 'localhost'); securityModeInput.value = 'auto'; } else { selectValue(dnsInputs, 'single'); selectValue(tlsInputs, detectedUrl.protocol === 'https:' ? 'single' : 'none'); securityModeInput.value = 'auto'; } function setPreviewValue(id, value) { document.getElementById(id).value = value; } function updateUrlComparison(configuredUrl, expectedAdminOrigin) { var browserUrl = window.location.origin; var status = document.getElementById('archivebox-setup-url-match'); document.getElementById('archivebox-setup-browser-url').textContent = browserUrl; document.getElementById('archivebox-setup-configured-url').textContent = configuredUrl; if (browserUrl.toLowerCase() === configuredUrl.toLowerCase()) { status.className = 'abx-url-comparison-status is-match'; status.textContent = '✅ Browser URL matches BASE_URL.'; } else if (expectedAdminOrigin && browserUrl.toLowerCase() === expectedAdminOrigin.toLowerCase()) { status.className = 'abx-url-comparison-status is-match'; status.textContent = '✅ Browser URL matches admin.BASE_URL as expected.'; } else { status.className = 'abx-url-comparison-status is-warning'; status.textContent = '⚠️ Browser URL does not match BASE_URL' + (expectedAdminOrigin ? ' or its expected admin URL.' : '.'); } } function updatePreview() { var parsed; try { parsed = new URL(baseUrlInput.value.trim() || baseUrlInput.placeholder); if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') throw new Error('Unsupported URL scheme'); } catch (error) { currentPreview = null; updateUrlComparison(baseUrlInput.value.trim() || '(invalid BASE_URL)', ''); reviewButton.disabled = true; validationStatus.textContent = 'Enter a valid http:// or https:// BASE_URL to continue.'; ['archivebox-preview-admin', 'archivebox-preview-api', 'archivebox-preview-index', 'archivebox-preview-snapshot', 'archivebox-preview-save', 'archivebox-preview-last'].forEach(function(id) { setPreviewValue(id, 'Enter a valid http:// or https:// BASE_URL'); }); ['archivebox-preview-admin-status', 'archivebox-preview-api-status', 'archivebox-preview-index-status', 'archivebox-preview-snapshot-status', 'archivebox-preview-save-status', 'archivebox-preview-last-status'].forEach(function(id) { document.getElementById(id).textContent = '❌ Invalid BASE_URL'; }); return; } var hostname = parsed.hostname.toLowerCase(); var isLocalhost = hostname === 'localhost' || hostname.endsWith('.localhost'); var selectedHosting = selectedValue(hostingInputs); var selectedDnsMode = selectedValue(dnsInputs); var selectedTlsMode = selectedValue(tlsInputs); var selectedLocalhost = selectedHosting === 'localhost' && selectedDnsMode === 'localhost' && selectedTlsMode === 'localhost'; var usesSubdomains = securityModeInput.value === 'safe-subdomains-fullreplay' || (securityModeInput.value === 'auto' && selectedLocalhost); var fullJsReplay = usesSubdomains || securityModeInput.value === 'unsafe-onedomain-noadmin' || securityModeInput.value === 'danger-onedomain-fullreplay'; var controlPlaneEnabled = securityModeInput.value !== 'unsafe-onedomain-noadmin'; var baseHost = parsed.host; var originFor = function(role) { return parsed.protocol + '//' + (usesSubdomains ? role + '.' + baseHost : baseHost); }; var adminOrigin = originFor('admin'); var webOrigin = originFor('web'); var apiOrigin = originFor('api'); var snapshotOrigin = usesSubdomains ? parsed.protocol + '//snap-456789abcdef.' + baseHost : webOrigin + '/snapshot/0123456789abcdef0123456789abcdef'; var originalOrigin = webOrigin + '/original/reddit.com'; var permission = permissionsInput.value; var httpsReady = selectedTlsMode === 'wildcard' || selectedTlsMode === 'single' || selectedTlsMode === 'localhost'; var effectiveMode = document.getElementById('archivebox-setup-effective-mode'); document.getElementById('archivebox-setup-wildcard-example').textContent = 'https://*.' + parsed.host; document.getElementById('archivebox-setup-base-url-example').textContent = parsed.origin; updateUrlComparison(parsed.origin, usesSubdomains ? adminOrigin : parsed.origin); if (securityModeInput.value === 'auto') { effectiveMode.textContent = selectedLocalhost ? 'Effective result for this BASE_URL: isolated full replay. Localhost needs no DNS or HTTPS setup.' : 'Effective result for this BASE_URL: one-domain replay with archived JavaScript disabled. Only one DNS record is needed; HTTPS unlocks service-worker replay viewers.'; } else if (securityModeInput.value === 'safe-onedomain-nojsreplay') { effectiveMode.textContent = 'Effective result: one-domain replay with archived JavaScript disabled. Only one DNS record is needed; ' + (httpsReady ? 'high-fidelity replay viewers are available.' : 'add HTTPS to enable service-worker replay viewers.'); } else if (securityModeInput.value === 'safe-subdomains-fullreplay') { effectiveMode.textContent = selectedLocalhost ? 'Effective result: isolated full replay with no manual DNS or HTTPS setup.' : 'Effective result: isolated full replay. Configure wildcard DNS and a wildcard HTTPS certificate for all replay features.'; } else if (securityModeInput.value === 'unsafe-onedomain-noadmin') { effectiveMode.textContent = 'Effective result: full replay on one shared domain, with login, admin, API, submissions, and mutations disabled.'; } else { effectiveMode.textContent = 'Effective result: full replay and privileged UI/API share one domain. Malicious archived JavaScript can compromise the archive and your browser session.'; } setPreviewValue('archivebox-preview-admin', adminOrigin + '/admin/'); setPreviewValue('archivebox-preview-api', apiOrigin + '/api/v1/docs'); setPreviewValue('archivebox-preview-index', webOrigin + '/public/'); setPreviewValue('archivebox-preview-snapshot', snapshotOrigin + '/'); setPreviewValue('archivebox-preview-save', webOrigin + '/web/https://example.com'); setPreviewValue('archivebox-preview-last', originalOrigin + '/r/somesubpage'); var routeSemantics = { admin: controlPlaneEnabled ? 'Admin login required' : 'Disabled in this mode', api: controlPlaneEnabled ? 'Available; access-controlled' : 'Disabled in this mode', index: publicIndexInput.checked ? 'Anonymous index enabled' : 'Sign-in required', snapshot: permission === 'private' ? (controlPlaneEnabled ? 'Admin only by default' : 'Private content unavailable') : (permission === 'unlisted' ? 'Anyone with URL' : 'Anonymous and listed'), save: controlPlaneEnabled ? (publicAddInput.checked ? 'Anonymous submissions' : 'Admin only') : 'Submissions disabled', last: permission === 'private' ? (controlPlaneEnabled ? 'Admin only by default' : 'Private content unavailable') : 'Anonymous direct access', }; document.getElementById('archivebox-preview-admin-status').textContent = '⏳ Testing · ' + routeSemantics.admin; document.getElementById('archivebox-preview-api-status').textContent = '⏳ Testing · ' + routeSemantics.api; document.getElementById('archivebox-preview-index-status').textContent = '⏳ Testing · ' + routeSemantics.index; document.getElementById('archivebox-preview-snapshot-status').textContent = '⏳ Testing host · ' + routeSemantics.snapshot; document.getElementById('archivebox-preview-save-status').textContent = '⏳ Testing web host · ' + routeSemantics.save; document.getElementById('archivebox-preview-last-status').textContent = '⏳ Testing host · ' + routeSemantics.last; document.getElementById('archivebox-preview-dns').textContent = selectedDnsMode === 'localhost' ? '✅ No manual DNS setup is needed with localhost.' : (selectedDnsMode === 'wildcard' ? '✱ Configure wildcard DNS for the base hostname and all *.hostname subdomains.' : (selectedDnsMode === 'single' ? '🔢 Configure one A/AAAA/CNAME or /etc/hosts entry for the base hostname.' : '❌ Choose a DNS mode.')); document.getElementById('archivebox-preview-tls').textContent = selectedTlsMode === 'localhost' ? '✅ No HTTPS setup is needed with localhost.' : (selectedTlsMode === 'wildcard' ? '✱ Configure a browser-trusted wildcard HTTPS certificate.' : (selectedTlsMode === 'single' ? '🔒 Configure one browser-trusted HTTPS certificate for the base hostname.' : (selectedTlsMode === 'none' ? '⚠️ Direct HTTP selected; in-browser WARC viewing will not work.' : '❌ Choose an ingress and TLS mode.'))); document.getElementById('archivebox-preview-warc').textContent = httpsReady ? '✅ Available' : '❌ Requires HTTPS (or localhost)'; document.getElementById('archivebox-preview-js').textContent = fullJsReplay ? (usesSubdomains ? '✅ Available with per-snapshot isolation' : '⚠️ Available on the shared archive origin') : '❌ Archived JavaScript is disabled'; var risks = []; if (usesSubdomains) { risks.push('✅ Snapshot replay origins are isolated from the admin UI, API, and other snapshots.'); } else if (securityModeInput.value === 'unsafe-onedomain-noadmin') { risks.push('⚠️ Untrusted archived JavaScript can read any anonymous public or unlisted archive content on the shared origin. Admin UI, API, login, submissions, and all state-changing requests are disabled; private snapshots are unavailable because every visitor is anonymous.'); } else if (securityModeInput.value === 'danger-onedomain-fullreplay') { risks.push('🛑 Malicious archived JavaScript shares an origin with the archive index, every reachable snapshot, saved headers, admin UI, and REST API. It can trivially read sensitive data and use your authenticated browser to change configuration, install or invoke binaries, archive intranet URLs, or delete data. Use only on a disposable isolated server with no secrets or trusted browser session.'); } else { risks.push('⚠️ UI, API, and archive replay share one origin. CSP disables risky archived scripts, but a CSP or content-type bypass could expose the archive index, other snapshots, saved headers, admin pages, API data, and canonical-host mutations.'); } risks.push(publicIndexInput.checked ? '⚠️ Anonymous visitors can enumerate public snapshot URLs and titles; saved URLs may contain private share tokens or other secrets.' : '✅ Anonymous visitors cannot browse the snapshot index.'); risks.push(!controlPlaneEnabled ? '✅ URL submission and other state-changing requests are disabled for everyone in this replay-only mode.' : (publicAddInput.checked ? '⚠️ Anonymous visitors can submit malicious or private/intranet URLs. A filtering or per-crawl configuration bypass could expose internal content or threaten the server.' : '✅ Only signed-in admins can submit new URLs.')); risks.push(permission === 'public' ? '⚠️ New snapshots are listed and readable anonymously. Replayed pages, metadata, headers, cookies, PII, and API keys captured in an archive may become public.' : (permission === 'unlisted' ? '⚠️ New snapshots are hidden from listings but remain readable by anyone who discovers or receives their URL.' : (controlPlaneEnabled ? '✅ New snapshots require an authenticated ArchiveBox admin by default.' : '⚠️ Private snapshots cannot be viewed while the replay-only mode disables authentication.'))); document.getElementById('archivebox-setup-risks').innerHTML = risks.map(function(risk) { return '