<!doctype html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>holger — artifacts at rest</title>
<style>
:root {
color-scheme: light;
--bg: #fefefe;
--bg-rgb: 254 254 254;
--surface: oklch(1 0 0);
--raised: #e5e5e3;
--line: oklch(0.885 0.005 75);
--line-strong: oklch(0.775 0.006 75);
--ink: oklch(0.225 0.008 70);
--ink-dim: oklch(0.435 0.008 70);
--ink-mute: oklch(0.565 0.007 70);
--pass: oklch(0.52 0.15 155);
--bad: oklch(0.53 0.20 25);
--sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, monospace;
--display: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0; display: flex; min-height: 100dvh;
background: var(--bg); color: var(--ink);
font: 14.5px/1.55 var(--sans); -webkit-font-smoothing: antialiased;
}
:root { --backdrop-image: url("/holger.webp"); }
.art {
flex: 0 0 50%;
background-color: var(--bg);
background-image:
linear-gradient(to right, rgb(var(--bg-rgb) / 0) calc(100% - 90px), rgb(var(--bg-rgb) / 1)),
var(--backdrop-image);
background-size: auto, contain;
background-position: center, center;
background-repeat: no-repeat, no-repeat;
}
.side {
flex: 1 1 50%; min-width: 0; height: 100dvh; overflow: hidden;
display: flex; flex-direction: column; gap: 1.6rem;
padding: clamp(1.25rem, 3.2vw, 2.75rem);
}
.entry { flex: 0 0 auto; }
.tagline {
margin: 0 0 1.25rem; font-family: var(--display); font-size: 1.05rem;
color: var(--ink-dim);
}
h1 {
margin: 0 0 .35rem;
font-family: var(--display);
font-size: 2.1rem; font-weight: 500; letter-spacing: -.012em;
}
.by {
flex: 0 0 auto;
display: flex; align-items: center; flex-wrap: wrap; gap: .38rem;
margin: auto 0 0; font-family: var(--display); font-size: .95rem; color: var(--ink-dim);
}
.by strong { font-weight: 600; color: var(--ink); }
.by a { color: inherit; border-radius: 3px; }
button {
width: 100%; padding: .62rem .8rem; border-radius: 6px;
border: 1px solid transparent; background: var(--raised); color: var(--ink);
font: inherit; font-size: .92rem; font-weight: 550; cursor: pointer;
transition: background .15s cubic-bezier(.16,1,.3,1), border-color .15s cubic-bezier(.16,1,.3,1);
}
button:hover:not(:disabled) { background: var(--raised); border-color: var(--ink-mute); }
button:disabled { opacity: .45; cursor: not-allowed; }
button.primary { background: var(--ink); border-color: var(--ink); color: var(--surface); }
button.primary:hover:not(:disabled) { background: oklch(0 0 0); border-color: oklch(0 0 0); }
button.primary.working {
background: var(--surface); color: var(--ink); border-color: var(--ink);
}
button.working:disabled { opacity: 1; cursor: progress; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 5px; }
.msg { margin-top: .7rem; min-height: 1.2rem; font-size: .82rem; color: var(--ink-dim); }
.msg.err { color: var(--bad); font-family: var(--mono); overflow-wrap: anywhere; }
.msg.ok { color: var(--pass); }
.offer { margin: .9rem 0 0; color: var(--ink-dim); font-size: .86rem; }
.offer a { color: var(--ink); }
.offer code { font-family: var(--mono); font-size: .92em; }
.browse { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: .55rem; }
.browse h2 {
flex: 0 0 auto; margin: .2rem 0 0; font-size: .72rem; font-weight: 600;
letter-spacing: .08em; text-transform: uppercase; color: #000;
}
#releases { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
table { width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: .88rem; }
thead th {
position: sticky; top: 0; z-index: 1; background: var(--bg);
text-align: left; font-weight: 600; font-size: .72rem; letter-spacing: .06em;
text-transform: uppercase; color: var(--ink-mute);
padding: .3rem .5rem; border-bottom: 1px solid var(--line);
}
tbody td {
padding: .32rem .5rem; color: #000;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 0;
}
tbody tr { border-radius: 5px; }
tbody tr:hover td { background: var(--raised); }
td.version, th.version { text-align: right; width: 1%; white-space: nowrap; max-width: none; }
.browse[hidden] { display: none; }
.empty { margin: .4rem .5rem; color: var(--ink-mute); font-size: .86rem; font-family: var(--sans); }
.roster { flex: 0 0 auto; max-height: 9.5rem; overflow-y: auto; margin: 0; padding: 0; list-style: none; }
.roster li {
display: flex; align-items: baseline; gap: .5rem;
padding: .28rem .5rem; font-family: var(--mono); font-size: .88rem; color: #000;
}
.roster li:hover { background: var(--raised); border-radius: 5px; }
.roster .fmt { color: var(--ink-mute); font-size: .78rem; }
.roster .count { margin-left: auto; color: var(--ink-mute); font-size: .78rem; }
#repos { flex: 0 0 auto; }
@media (max-width: 720px) {
body { display: block; }
.art {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
opacity: .16; filter: saturate(.9);
}
.side {
position: relative; z-index: 1; width: 100%;
height: auto; min-height: 100dvh; overflow: visible;
padding: 1.25rem 1.1rem; gap: 1.2rem;
}
#releases { overflow: visible; }
button { border-color: var(--line-strong); }
thead th { background: var(--bg); }
}
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
</style>
</head>
<body>
<div class="art" aria-hidden="true"></div>
<div class="side">
<main class="entry">
<h1>Holger</h1>
<p class="tagline">Immutable artifact repository in pure Rust</p>
<button id="enter" class="primary" disabled> </button>
<div id="msg" class="msg" role="status" aria-live="polite">reading the server …</div>
<p class="offer" id="offer" hidden>Point a package manager at <code id="base-url"></code></p>
<p class="offer" id="register-offer" hidden>No passkey on this console yet? <a id="register-link" href="/login">Register one</a></p>
</main>
<section class="browse" id="repos" hidden>
<h2 id="repos-heading">Public repositories</h2>
<ul class="roster" id="repos-list" aria-labelledby="repos-heading"></ul>
</section>
<section class="browse" id="browse" hidden>
<h2 id="releases-heading">Latest releases</h2>
<div id="releases">
<table aria-labelledby="releases-heading">
<thead>
<tr>
<th scope="col">Repository</th>
<th scope="col">Artifact</th>
<th scope="col" class="version">Version</th>
</tr>
</thead>
<tbody id="releases-body"></tbody>
</table>
<p class="empty" id="releases-empty" hidden></p>
</div>
</section>
<p class="by">
brought to you by
<a class="maker" href="https://www.vetra.se" target="_blank" rel="noopener noreferrer">
<img class="mark" src="/vetra.svg" width="96" height="18" alt="" aria-hidden="true">
<strong>Vetra AB</strong>
</a>
&
<img class="mark" src="/ignalina.png" width="18" height="18" alt="" aria-hidden="true">
<strong>Ignalina ApS</strong>
</p>
</div>
<script>
const msg = document.getElementById('msg');
function show(text, cls) { msg.textContent = text; msg.className = 'msg ' + (cls || ''); }
(function entry() {
const btn = document.getElementById('enter');
fetch('/-/front', { credentials: 'omit' })
.then(r => {
if (r.status === 401 || r.status === 403) {
throw new Error('holger-front-3: this server gates reads behind a credential, so its front page cannot name itself. Configure `require_auth_for_reads: false`, or read the console instead.');
}
if (r.status === 404) {
throw new Error('holger-front-4: this build of holger-server has no `/-/front` door. The page is newer than the server behind it.');
}
if (!r.ok) throw new Error('holger-front-4: /-/front answered ' + r.status);
return r.json();
})
.then(front => {
if (front.base_url) {
document.getElementById('base-url').textContent = front.base_url;
document.getElementById('offer').hidden = false;
}
if (front.login && front.login.label) {
btn.textContent = front.login.label;
btn.disabled = false;
btn.dataset.start = front.login.start || '';
if (front.login.door === 'here' && front.login.finish) {
btn.dataset.door = 'here';
btn.dataset.finish = front.login.finish;
btn.dataset.next = front.login.next || '/';
const reg = document.getElementById('register-offer');
const link = document.getElementById('register-link');
if (reg && link && rooted(front.login.start)) {
link.href = '/login';
reg.hidden = false;
}
}
show('');
} else {
btn.textContent = 'No browser login';
show('holger-front-2: this server offers no browser login. Its doors are mTLS, OIDC and bearer — for cargo, pip, docker and the console, not for a tab.', 'err');
}
})
.catch(e => {
btn.textContent = 'Unavailable';
show(String(e && e.message || e), 'err');
});
btn.onclick = function () {
const start = btn.dataset.start;
if (!rooted(start)) return;
if (btn.dataset.door === 'here') { ceremony(btn); return; }
location.href = start;
};
})();
function rooted(start) {
if (typeof start !== 'string' || start === '') return false;
if (!start.startsWith('/') || start.startsWith('//') || start.startsWith('/\\')) return false;
if (/[\x00-\x20\x7f]/.test(start)) return false;
return true;
}
function b64urlToBuf(s) {
s = s.replace(/-/g, '+').replace(/_/g, '/');
while (s.length % 4) s += '=';
const bin = atob(s), bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
return bytes.buffer;
}
function bufToB64url(buf) {
const bytes = new Uint8Array(buf);
let s = '';
for (const b of bytes) s += String.fromCharCode(b);
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
async function ceremony(btn) {
const label = btn.textContent;
const start = btn.dataset.start, finish = btn.dataset.finish;
const next = rooted(btn.dataset.next) ? btn.dataset.next : '/';
if (!rooted(start) || !rooted(finish)) return;
btn.disabled = true;
btn.classList.add('working');
btn.textContent = 'Processing …';
try {
if (!window.PublicKeyCredential || !navigator.credentials) {
throw new Error('this browser has no passkey support');
}
show('waiting for your key …');
const res = await fetch(start, { credentials: 'same-origin' });
if (!res.ok) throw new Error(await res.text() || 'could not start');
const begin = await res.json();
const pk = begin.publicKey;
pk.challenge = b64urlToBuf(pk.challenge);
pk.allowCredentials = (pk.allowCredentials || []).map(c => ({ ...c, id: b64urlToBuf(c.id) }));
const cred = await navigator.credentials.get({ publicKey: pk });
const body = { ceremony_id: begin.ceremony_id, id: cred.id, response: {
clientDataJSON: bufToB64url(cred.response.clientDataJSON),
authenticatorData: bufToB64url(cred.response.authenticatorData),
signature: bufToB64url(cred.response.signature),
}};
const fin = await fetch(finish, {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
if (fin.ok) { show('logged in — opening the console …', 'ok'); location.href = next; }
else show(await fin.text() || 'login was denied', 'err');
} catch (e) { show(e.message || String(e), 'err'); }
finally {
btn.disabled = false;
btn.classList.remove('working');
btn.textContent = label;
}
}
(function catalogue() {
const panel = document.getElementById('browse');
const body = document.getElementById('releases-body');
const empty = document.getElementById('releases-empty');
const repos = document.getElementById('repos');
const roster = document.getElementById('repos-list');
if (!panel || !body) return;
fetch('/-/releases', { credentials: 'omit' })
.then(r => (r.ok ? r.json() : null))
.then(doc => {
if (!doc || typeof doc !== 'object') return;
const names = doc.repositories;
if (roster && repos && Array.isArray(names) && names.length > 0) {
for (const r of names) {
const li = document.createElement('li');
const name = document.createElement('span');
name.textContent = String(r.name === undefined ? '' : r.name);
li.appendChild(name);
if (r.format) {
const fmt = document.createElement('span');
fmt.className = 'fmt';
fmt.textContent = String(r.format);
li.appendChild(fmt);
}
const count = document.createElement('span');
count.className = 'count';
const n = r.packages;
count.textContent = (n === 1 ? '1 package' : String(n === undefined ? 0 : n) + ' packages');
li.appendChild(count);
roster.appendChild(li);
}
repos.hidden = false;
}
const rows = doc.releases;
panel.hidden = false;
if (!Array.isArray(rows) || rows.length === 0) {
document.querySelector('#releases table').hidden = true;
empty.textContent = 'Nothing published yet.';
empty.hidden = false;
return;
}
for (const row of rows) {
const tr = document.createElement('tr');
for (const [key, cls] of [['repository', ''], ['artifact', ''], ['version', 'version']]) {
const td = document.createElement('td');
if (cls) td.className = cls;
td.textContent = String(row[key] === undefined ? '' : row[key]);
td.title = td.textContent;
tr.appendChild(td);
}
body.appendChild(tr);
}
})
.catch(() => {
});
})();
</script>
</body>
</html>