Skip to content

Instantly share code, notes, and snippets.

@Cyang39
Created December 14, 2025 14:11
Show Gist options
  • Select an option

  • Save Cyang39/7ecb68e2ee2f6ffc644977f10d9af4e1 to your computer and use it in GitHub Desktop.

Select an option

Save Cyang39/7ecb68e2ee2f6ffc644977f10d9af4e1 to your computer and use it in GitHub Desktop.
feather wiki nests server bun version
#!/usr/bin/env bun
// == Introduction
// Source: https://codeberg.org/Alamantus/FeatherWiki/src/branch/main/nests/server.mjs
// Why bun version? Because running the original Node script with Bun causes crashes.
// Tip1: Run `bun run server.mjs`
// Tip2: Run `docker run -d -p 4505:4505 -v "$PWD":/app -w /app oven/bun:latest bun run server.mjs`
// == Introduction End
// == Configuration
const userpass = null;
// use the line below instead to enable basic authentication
// const userpass = "user:pass";
// where the Feather Wiki file is located
const wikifile = "index.html";
const hostname = "0.0.0.0";
const port = 4505;
// == Configuration End
let mutexId = 0;
const waitingResolvers = [];
let currentMutexId = null;
function acquireMutex() {
if (!currentMutexId) {
currentMutexId = mutexId++;
return Promise.resolve(currentMutexId);
}
return new Promise((resolve) => waitingResolvers.push(resolve));
}
function releaseMutex(id) {
if (id !== currentMutexId) {
throw new Error(`Release ID doesn't match current lock ID`);
}
if (waitingResolvers.length > 0) {
const resolve = waitingResolvers.shift();
currentMutexId = mutexId++;
resolve(currentMutexId);
} else {
currentMutexId = null;
}
}
function notAuthorized() {
return new Response(null, {
status: 401,
headers: {
"WWW-Authenticate": `Basic realm="FeatherWiki"`,
},
});
}
function checkBasicAuth(request) {
if (userpass == null) return null;
const authorization = request.headers.get("Authorization");
if (!authorization) return notAuthorized();
const match = /Basic (.*)/.exec(authorization);
if (!match) return notAuthorized();
const given_userpass = atob(match[1]);
console.log("Received Authorization:", given_userpass);
if (given_userpass !== userpass) return notAuthorized();
return null;
}
Bun.serve({
hostname,
port,
async fetch(request) {
// Basic auth (if enabled)
const authResp = checkBasicAuth(request);
if (authResp) return authResp;
switch (request.method) {
case "GET": {
const id = await acquireMutex();
try {
const body = await Bun.file(wikifile).text();
return new Response(body, {
status: 200,
headers: { "Content-Type": "text/html" },
});
} finally {
releaseMutex(id);
}
}
case "PUT": {
const id = await acquireMutex();
try {
const body = await request.text();
await Bun.write(wikifile, body);
return new Response(null, { status: 204 });
} finally {
releaseMutex(id);
}
}
case "OPTIONS": {
return new Response(null, {
status: 204,
headers: {
DAV: "1",
Allow: "GET, PUT, OPTIONS",
"Content-Length": "0",
},
});
}
default:
return new Response(null, { status: 500 });
}
},
});
console.log(`Server running at http://${hostname}:${port}/`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment