This commit is contained in:
2026-03-13 15:21:35 -04:00
commit 2c539123bf
4 changed files with 761 additions and 0 deletions

585
index.html Normal file
View File

@@ -0,0 +1,585 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Everest Linux - Package Repository</title>
<link rel="icon" type="image/x-icon" href="logo.svg"/>
<style>
/* === Palette from k2.css === */
:root {
--bg: #1E1E1E;
--bg-dark: #0F0F0F;
--bg-darker: #131313;
--bg-mid: #2C2C2C;
--text: #bfbfbf;
--text-bright: #e6e6e6;
--blue: #4a86e8;
--green: MediumSeaGreen;
--purple: SlateBlue;
--yellow: #ffcc00;
--red: #e60000;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
background-color: var(--bg);
color: var(--text);
font-family: sans-serif;
}
/* === Sidebar (verbatim k2.css) === */
.sidenav {
height: 100%;
width: 200px;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: var(--bg-darker);
overflow-x: hidden;
padding-top: 20px;
}
.sidenav img {
display: block;
width: 120px;
margin: 0 auto 4px;
padding: 8px;
}
.sidenav a {
padding: 6px 8px 6px 16px;
text-decoration: none;
font-size: 20px;
color: var(--text);
display: block;
}
.sidenav a:hover, .sidenav a.active { color: var(--blue); }
/* === Hero (verbatim k2.css) === */
.hero-image {
background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("banner.jpg");
height: 55%;
background-position: 50% 30%;
background-repeat: no-repeat;
background-size: cover;
position: relative;
padding-top: 0;
padding-right: 0;
margin-right: -8px;
margin-top: -8px;
}
.hero-text {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
}
.hero-text h1 { font-size: 32px; font-weight: 700; }
.hero-text p { font-size: 15px; color: #ccc; margin-top: 6px; font-family: monospace; }
/* === Main === */
.main {
margin-left: 200px;
padding: 0 10px;
background-color: var(--bg);
}
/* === h2 (verbatim k2.css) === */
h2 {
border-left: 6px solid var(--blue);
padding-left: 4px;
border-radius: 5px;
color: var(--text);
font-size: 20px;
margin: 20px 0 8px;
}
h3 { color: var(--text-bright); font-size: 15px; margin: 12px 0 6px; }
p { margin: 6px 0; }
/* === Callout divs (k2.css) === */
.note { background-color: #1f2a2a; border-left: 4px solid var(--blue); border-radius: 2px; padding: 4px; width: 50%; color: var(--text); margin: 4px 0; }
.caution { background-color: #332b00; border-left: 4px solid var(--yellow); border-radius: 2px; padding: 4px; width: 50%; color: var(--yellow); margin: 4px 0; }
.warning { background-color: #330000; border-left: 4px solid var(--red); border-radius: 2px; padding: 4px; width: 50%; color: var(--red); margin: 4px 0; }
.news { background-color: var(--bg-darker); border-left: 4px solid var(--blue); border-radius: 2px; padding: 4px; width: 50%; color: var(--text); margin: 4px 0; }
.file { font-family: monospace; background-color: var(--bg-dark); border-left: 4px solid MediumSeaGreen; border-radius: 2px; padding: 4px; width: 50%; color: var(--text); margin: 4px 0; }
.kernel { font-family: monospace; background-color: var(--bg-dark); border-left: 4px solid SlateBlue; border-radius: 2px; padding: 4px; width: 50%; color: var(--text); margin: 4px 0; }
.bigcode { font-family: monospace; background-color: var(--bg-dark); border-left: 4px solid var(--text); border-radius: 2px; padding: 6px; width: 50%; margin: 4px 0; white-space: pre; overflow-x: auto; }
/* === Custom elements (k2.css) === */
notehead { display: block; background-color: #1f2a2a; border-left: 4px solid var(--blue); border-radius: 2px; padding: 6px; width: 50%; color: var(--blue); font-weight: 600; font-size: 14px; }
fhead { display: block; background-color: var(--bg-dark); border-left: 4px solid MediumSeaGreen; border-radius: 2px; padding: 6px; width: 50%; color: MediumSeaGreen; font-weight: 600; font-size: 14px; }
khead { display: block; background-color: var(--bg-dark); border-left: 4px solid SlateBlue; border-radius: 2px; padding: 6px; width: 50%; color: SlateBlue; font-weight: 600; font-size: 14px; }
cautionhead { display: block; background-color: #332b00; border-left: 4px solid var(--yellow); border-radius: 2px; padding: 6px; width: 50%; color: var(--yellow); font-weight: 600; font-size: 14px; }
warnhead { display: block; background-color: #330000; border-left: 4px solid var(--red); border-radius: 2px; padding: 6px; width: 50%; color: var(--red); font-weight: 600; font-size: 14px; }
bigcodehead { display: block; background-color: var(--bg-dark); border-left: 4px solid var(--text); border-radius: 2px; padding: 4px; width: 50%; color: var(--text); font-weight: 600; font-size: 14px; }
wikihead { display: block; border-left: 4px solid Tomato; border-radius: 2px; padding: 6px; width: 50%; color: Tomato; margin: 4px 0; }
wiki { display: block; border-left: 4px solid Tomato; border-radius: 2px; padding: 6px; width: 50%; margin: 4px 0; }
cil { font-family: monospace; background-color: var(--bg-dark); padding: 2px; }
/* === Inline code (k2.css) === */
code { font-family: monospace; background-color: var(--bg-dark); border-left: 4px solid var(--blue); border-radius: 2px; padding: 2px; color: var(--text); display: inline-block; margin: 2px 0; }
/* === Tables (k2.css) === */
table { border-collapse: collapse; }
table, td { border: 2px solid var(--text); padding: 4px; background-color: var(--bg-dark); color: var(--text); }
th { border: 2px solid var(--text); background-color: var(--bg-mid); color: var(--text-bright); padding: 4px; text-align: left; }
/* === Buttons (k2.css) === */
button {
font-size: 16px;
border: none;
border-left: 8px solid var(--blue);
padding: 8px 20px;
border-radius: 5px;
background-color: var(--bg-mid);
color: var(--text-bright);
cursor: pointer;
font-family: sans-serif;
}
button:hover { background-color: #252525; }
/* === HR (k2.css) === */
hr { color: var(--blue); width: 50%; border-width: 4px; border-radius: 5px; margin-left: 0; margin: 16px 0; }
/* === Search (k2.css #myInput) === */
#searchInput {
width: 378px;
font-size: 16px;
padding: 10px;
border: 2px solid var(--blue);
border-radius: 2px;
margin-bottom: 12px;
background-color: var(--bg-mid);
color: var(--text-bright);
font-family: monospace;
outline: none;
display: block;
}
/* === Tabs === */
.tabs { display: flex; border-bottom: 2px solid var(--blue); margin-top: 14px; }
.tab-btn {
padding: 7px 18px;
background: var(--bg-darker);
color: var(--text);
border: none;
border-radius: 0;
font-size: 15px;
cursor: pointer;
font-family: sans-serif;
}
.tab-btn.active { background: var(--bg-mid); color: var(--text-bright); border-bottom: 2px solid var(--blue); margin-bottom: -2px; }
.tab-btn:hover { background: var(--bg-mid); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }
/* === Stats bar === */
.stats-bar { display: flex; gap: 16px; flex-wrap: wrap; margin: 12px 0 4px; }
.stat { background: var(--bg-darker); border-left: 4px solid var(--blue); border-radius: 2px; padding: 8px 16px; }
.stat .stat-val { font-family: monospace; font-size: 22px; color: var(--blue); }
.stat .stat-label { font-size: 12px; color: #777; }
/* === Package cards === */
#pkgList { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.pkg-card {
font-family: monospace;
background-color: var(--bg-dark);
border-left: 4px solid MediumSeaGreen;
border-radius: 2px;
padding: 6px 10px;
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
width: 70%;
}
.pkg-name { font-size: 15px; color: MediumSeaGreen; min-width: 150px; }
.pkg-ver { font-size: 13px; color: SlateBlue; min-width: 80px; }
.pkg-arch { font-size: 12px; color: #777; min-width: 70px; }
.pkg-desc { flex: 1; font-size: 13px; color: var(--text); font-family: sans-serif; }
.pkg-size { font-size: 12px; color: #666; min-width: 70px; text-align: right; }
.pkg-dl a {
font-size: 13px;
padding: 2px 10px;
border-left: 4px solid var(--blue);
background-color: var(--bg-mid);
color: var(--text-bright);
text-decoration: none;
border-radius: 2px;
}
.pkg-dl a:hover { background-color: #252525; }
/* === Upload form === */
.upload-form { display: flex; flex-direction: column; gap: 10px; max-width: 460px; }
.upload-form label { font-size: 13px; color: #888; margin-bottom: 2px; display: block; }
.upload-form input[type=text],
.upload-form input[type=file],
.upload-form textarea,
.upload-form select {
width: 100%;
padding: 7px 10px;
background: var(--bg-mid);
border: 2px solid #333;
border-radius: 2px;
color: var(--text-bright);
font-family: monospace;
font-size: 14px;
outline: none;
}
.upload-form input:focus,
.upload-form textarea:focus,
.upload-form select:focus { border-color: var(--blue); }
.upload-form textarea { resize: vertical; min-height: 60px; }
#statusMsg { margin-top: 6px; font-family: monospace; font-size: 14px; }
/* === Footer === */
footer { text-align: center; font-size: 10px; padding: 20px; }
</style>
</head>
<body>
<!-- Navbar -->
<div class="sidenav">
<img src="logo.svg" alt="Everest Linux">
<a href="index.html" class="active">Packages</a>
<a href="https://everestlinux.org">Home</a>
<a href="https://everestlinux.org/about.html">About</a>
<a href="https://everestlinux.org/install.html">Install</a>
<a href="https://everestlinux.org/download.html">Downloads</a>
<a href="https://everestlinux.org/docs/home.html">Documentation</a>
<a href="https://git.everestlinux.org">Git &#8599;</a>
</div>
<!-- Hero -->
<div class="hero-image">
<div class="hero-text">
<h1>Package Repository</h1>
<p>glacier package host</p>
</div>
</div>
<!-- Main content -->
<div class="main">
<div class="stats-bar" id="statsBar">
<div class="stat"><div class="stat-val" id="statTotal">-</div><div class="stat-label">Total Packages</div></div>
<div class="stat"><div class="stat-val" id="statSections">-</div><div class="stat-label">Sections</div></div>
<div class="stat"><div class="stat-val" id="statSize">-</div><div class="stat-label">Total Size</div></div>
</div>
<div class="tabs">
<button class="tab-btn active" id="tab-browse" onclick="showTab('browse')">Browse Packages</button>
<button class="tab-btn" id="tab-upload" onclick="showTab('upload')">Upload</button>
<button class="tab-btn" id="tab-api" onclick="showTab('api')">API Docs</button>
<button class="tab-btn" id="tab-about" onclick="showTab('about')">About</button>
</div>
<!-- ===== BROWSE ===== -->
<div class="tab-panel active" id="panel-browse">
<h2>Browse Packages</h2>
<input type="text" id="searchInput" placeholder="Search packages..." oninput="filterPackages()">
<div id="pkgList">
<div class="note"><p>Loading packages...</p></div>
</div>
</div>
<!-- ===== UPLOAD ===== -->
<div class="tab-panel" id="panel-upload">
<h2>Upload a Package</h2>
<p>Upload a compiled package archive to the repository.</p>
<notehead><strong>NOTE:</strong></notehead>
<div class="note">
<p>Supported formats: <code>.tar.gz</code>, <code>.tar.xz</code>, <code>.epkg</code></p>
<p>Uploading a package with an existing name will overwrite the previous entry.</p>
</div>
<p></p>
<div class="upload-form">
<div>
<label>Package Name *</label>
<input type="text" id="upName" placeholder="e.g. bash">
</div>
<div>
<label>Version *</label>
<input type="text" id="upVersion" placeholder="e.g. 5.2.0">
</div>
<div>
<label>Description</label>
<textarea id="upDesc" placeholder="Short description of the package"></textarea>
</div>
<div>
<label>Architecture</label>
<select id="upArch">
<option value="x86_64">x86_64</option>
<option value="aarch64">aarch64</option>
<option value="any">any</option>
</select>
</div>
<div>
<label>Section</label>
<select id="upSection">
<option value="base">base</option>
<option value="core">core</option>
<option value="extra">extra</option>
<option value="community">community</option>
<option value="misc">misc</option>
</select>
</div>
<div>
<label>Package File *</label>
<input type="file" id="upFile">
</div>
<div>
<button onclick="uploadPackage()">Upload Package</button>
</div>
<div id="statusMsg"></div>
</div>
</div>
<!-- ===== API DOCS ===== -->
<div class="tab-panel" id="panel-api">
<h2>API Reference</h2>
<p>The repository exposes a REST API for use with the <code>glacier</code> package manager or any HTTP client.</p>
<h2>Endpoints</h2>
<notehead><strong>GET</strong> /api/packages</notehead>
<div class="note">
<p>Returns a JSON array of all packages in the repository.</p>
</div>
<p></p>
<notehead><strong>GET</strong> /api/packages/search?q=term</notehead>
<div class="note">
<p>Search packages by name or description. Returns a filtered JSON array.</p>
</div>
<p></p>
<notehead><strong>GET</strong> /api/packages/:name</notehead>
<div class="note">
<p>Returns metadata for a single package by name.</p>
</div>
<p></p>
<fhead><strong>POST</strong> /api/packages</fhead>
<div class="file">
<p>Upload a new package. Send request body as <cil>multipart/form-data</cil>.</p>
<p>Required fields: <cil>name</cil>, <cil>version</cil>, <cil>file</cil></p>
<p>Optional fields: <cil>description</cil>, <cil>arch</cil>, <cil>section</cil></p>
</div>
<p></p>
<khead><strong>DELETE</strong> /api/packages/:name</khead>
<div class="kernel">
<p>Delete a package by name. Removes both the archive file and its metadata entry.</p>
</div>
<p></p>
<notehead><strong>GET</strong> /packages/:filename</notehead>
<div class="note">
<p>Direct download URL for a package archive. Served as static content.</p>
</div>
<h2>Usage</h2>
<p>Sync the package database and install a package with glacier:</p>
<code>(root)# glacier sync</code>
<p></p>
<code>(root)# glacier install &lt;package-name&gt;</code>
<p>Upload a package with curl:</p>
<bigcodehead><strong>COMMAND:</strong> Upload via curl</bigcodehead>
<div class="bigcode">curl -X POST https://your-repo.vercel.app/api/packages \
-F "name=bash" \
-F "version=5.2.0" \
-F "description=The GNU Bourne Again shell" \
-F "arch=x86_64" \
-F "section=base" \
-F "file=@bash-5.2.0.tar.gz"</div>
<p>Example JSON response for a package entry:</p>
<bigcodehead><strong>RESPONSE:</strong> Package metadata</bigcodehead>
<div class="bigcode">{
"name": "bash",
"version": "5.2.0",
"description": "The GNU Bourne Again shell",
"arch": "x86_64",
"section": "base",
"filename": "bash-5.2.0.tar.gz",
"size": 4194304,
"uploadedAt": "2025-01-01T00:00:00.000Z",
"downloadUrl": "/packages/bash-5.2.0.tar.gz"
}</div>
</div>
<!-- ===== ABOUT ===== -->
<div class="tab-panel" id="panel-about">
<h2>About This Repository</h2>
<p>This is the official EverestLinux package repository, hosting compiled packages for the <code>glacier</code> package manager.</p>
<p></p>
<notehead><strong>NOTE:</strong></notehead>
<div class="note">
<p>This repository is self-hostable. Fork the project, deploy to Vercel, and point <code>glacier</code> at your instance URL.</p>
</div>
<h2>Repository Sections</h2>
<table>
<tr>
<th>Section</th>
<th>Description</th>
</tr>
<tr><td><cil>base</cil></td><td>Essential system packages required for a minimal installation</td></tr>
<tr><td><cil>core</cil></td><td>Core utilities and libraries</td></tr>
<tr><td><cil>extra</cil></td><td>Additional packages not included in core</td></tr>
<tr><td><cil>community</cil></td><td>Community-maintained packages</td></tr>
<tr><td><cil>misc</cil></td><td>Miscellaneous packages</td></tr>
</table>
<h2>Configuring glacier</h2>
<p>Point glacier at this repository by editing the repos config file:</p>
<fhead><strong>FILE:</strong> /etc/glacier/repos.conf</fhead>
<div class="file">
<p>[everest]</p>
<p>url = https://your-repo.vercel.app</p>
<p>enabled = yes</p>
</div>
<p>Then run:</p>
<code>(root)# glacier sync</code>
<h2>Persistent Storage</h2>
<cautionhead><strong>CAUTION:</strong></cautionhead>
<div class="caution">
<p>Vercel's serverless filesystem is ephemeral. Uploaded packages will not survive redeployments.</p>
<p>For production use, replace the file storage backend with an S3-compatible service such as Cloudflare R2 or AWS S3, and the metadata store with a hosted database.</p>
</div>
<p></p>
<wikihead><strong>WIKI</strong>: <a href="https://git.everestlinux.org/EverestLinux/wiki/wiki/Introduction-to-Glacier">Introduction to Glacier</a></wikihead>
<p></p>
<hr>
</div>
</div>
<footer>
<p>Copyright (C) 2021-2025 Everest Linux</p>
<p>Linux (R) is a registered trademark of Linus Torvalds.</p>
<p>Everest Linux is provided AS IS, WITHOUT WARRANTY.</p>
</footer>
<script>
function showTab(name) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById('panel-' + name).classList.add('active');
document.getElementById('tab-' + name).classList.add('active');
}
let allPackages = [];
async function loadPackages() {
try {
const res = await fetch('/api/packages');
allPackages = await res.json();
renderPackages(allPackages);
updateStats(allPackages);
} catch (e) {
document.getElementById('pkgList').innerHTML =
'<div class="warning"><p>Could not connect to package API. Is the server running?</p></div>';
}
}
function formatBytes(bytes) {
if (!bytes) return '?';
if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / 1048576).toFixed(1) + ' MB';
}
function renderPackages(pkgs) {
const list = document.getElementById('pkgList');
if (!pkgs.length) {
list.innerHTML = '<div class="note"><p>No packages found.</p></div>';
return;
}
list.innerHTML = pkgs.map(p => `
<div class="pkg-card">
<span class="pkg-name">${esc(p.name)}</span>
<span class="pkg-ver">${esc(p.version)}</span>
<span class="pkg-arch">[${esc(p.arch)}]</span>
<span class="pkg-desc">${esc(p.description || '-')}</span>
<span class="pkg-size">${formatBytes(p.size)}</span>
<span class="pkg-dl"><a href="${esc(p.downloadUrl)}" download>Download</a></span>
</div>
`).join('');
}
function updateStats(pkgs) {
const sections = new Set(pkgs.map(p => p.section)).size;
const totalSize = pkgs.reduce((a, p) => a + (p.size || 0), 0);
document.getElementById('statTotal').textContent = pkgs.length;
document.getElementById('statSections').textContent = sections || '-';
document.getElementById('statSize').textContent = formatBytes(totalSize);
}
function filterPackages() {
const q = document.getElementById('searchInput').value.toLowerCase();
renderPackages(allPackages.filter(p =>
p.name.toLowerCase().includes(q) ||
(p.description || '').toLowerCase().includes(q) ||
(p.section || '').toLowerCase().includes(q)
));
}
function esc(s) {
return String(s)
.replace(/&/g,'&amp;').replace(/</g,'&lt;')
.replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
async function uploadPackage() {
const name = document.getElementById('upName').value.trim();
const version = document.getElementById('upVersion').value.trim();
const desc = document.getElementById('upDesc').value.trim();
const arch = document.getElementById('upArch').value;
const section = document.getElementById('upSection').value;
const file = document.getElementById('upFile').files[0];
const status = document.getElementById('statusMsg');
if (!name || !version || !file) {
status.innerHTML = '<span style="color:var(--red);">[x] Name, version, and file are required.</span>';
return;
}
status.innerHTML = '<span style="color:#888;">Uploading...</span>';
const fd = new FormData();
fd.append('name', name);
fd.append('version', version);
fd.append('description', desc);
fd.append('arch', arch);
fd.append('section', section);
fd.append('file', file);
try {
const res = await fetch('/api/packages', { method: 'POST', body: fd });
const data = await res.json();
if (res.ok) {
status.innerHTML = `<span style="color:MediumSeaGreen;">[+] Package '${esc(data.name)}-${esc(data.version)}' uploaded successfully.</span>`;
loadPackages();
['upName','upVersion','upDesc'].forEach(id => document.getElementById(id).value = '');
document.getElementById('upFile').value = '';
} else {
status.innerHTML = `<span style="color:var(--red);">[x] Error: ${esc(data.error)}</span>`;
}
} catch (e) {
status.innerHTML = `<span style="color:var(--red);">[x] Network error: ${esc(e.message)}</span>`;
}
}
loadPackages();
</script>
</body>
</html>