From a25028aa30bf0f3b89a9a7c99192e1a14267fc97 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Mon, 11 Sep 2023 14:52:07 +0200 Subject: Initial commit --- package.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 package.json (limited to 'package.json') diff --git a/package.json b/package.json new file mode 100644 index 0000000..96a8252 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "homestead", + "module": "src/index.ts", + "devDependencies": { + "bun-html-live-reload": "^0.1.1", + "bun-types": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "type": "module", + "dependencies": { + "siopao": "^0.4.0" + } +} \ No newline at end of file -- cgit 1.4.1 From 51cc4389f6dc7947ee34d1b3367876941e8a8fbc Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Mon, 11 Sep 2023 19:43:06 +0200 Subject: Serve static files --- README.md | 5 +++-- bun.lockb | Bin 2354 -> 2042 bytes package.json | 2 +- src/index.ts | 12 ++++++------ test/index.test.ts | 9 ++++++++- 5 files changed, 18 insertions(+), 10 deletions(-) (limited to 'package.json') diff --git a/README.md b/README.md index 3430625..34f3f42 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ ## Goals -1. To be a near-drop-in replacement for Zola -2. More indieweb features +1. Static web server with prometheus-based analytics +2. Dynamic web server capable of generating Zola-based websites +3. More indieweb features ## Installing diff --git a/bun.lockb b/bun.lockb index 2ab9dd9..f724266 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 96a8252..d0e61be 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,6 @@ }, "type": "module", "dependencies": { - "siopao": "^0.4.0" + "serve-static-bun": "^0.5.3" } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 8c0a3ba..59913b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -import { withHtmlLiveReload } from "bun-html-live-reload"; -import Siopao from "siopao"; +import { withHtmlLiveReload } from "bun-html-live-reload" +import serveStatic from "serve-static-bun" -const router = new Siopao(); - -router.get("/status", () => new Response("OK")); +const dir = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "./" export default withHtmlLiveReload({ - fetch: router.fetch.bind(router), + fetch: serveStatic(dir, { + dotfiles: "allow" + }), }) diff --git a/test/index.test.ts b/test/index.test.ts index d82cb9e..8a32e47 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -4,6 +4,7 @@ import { expect, test, beforeAll, afterAll } from "bun:test" import app from "../src/index" const port = 33000; +const base = `http://localhost:${port}/`; let server: Server beforeAll(async function () { @@ -15,7 +16,13 @@ afterAll(function () { }) test("/status returns 200 OK", async function () { - const res = await fetch(`http://localhost:${port}/status`) + const res = await fetch(new URL("/status", base)) expect(res.status).toBe(200) expect(await res.text()).toBe("OK") }) + +test("/ returns 200 and says Hello world", async function () { + const res = await fetch(base) + expect(res.status).toBe(200) + expect(await res.text()).toBe("Hello world") +}) -- cgit 1.4.1 From 78439b16cc66532225e75c9aa40cf7c49cddc22d Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Tue, 12 Sep 2023 10:56:39 +0200 Subject: Read config from TOML file --- bun.lockb | Bin 2042 -> 2386 bytes package.json | 3 ++- src/config.ts | 9 +++++++++ src/index.ts | 10 ++++++++-- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/config.ts (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index f724266..c8b2651 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index d0e61be..27f95e0 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "type": "module", "dependencies": { - "serve-static-bun": "^0.5.3" + "serve-static-bun": "^0.5.3", + "toml": "^3.0.0" } } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..064b038 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,9 @@ +import path from "node:path"; +import fs from "node:fs"; +import toml from "toml"; + +export default function readConfig(base: string) { + const filename = path.join(base, "config.toml"); + + return toml.parse(fs.readFileSync(filename, "utf-8")); +} diff --git a/src/index.ts b/src/index.ts index 4887dd6..d8c9bf1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,16 @@ +import path from "node:path"; import { withHtmlLiveReload } from "bun-html-live-reload"; import serveStatic from "serve-static-bun"; -const dir = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "./"; +import readConfig from "./config"; + +const base = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "."; + +const config = readConfig(base); export default withHtmlLiveReload({ - fetch: serveStatic(dir, { + fetch: serveStatic(path.join(base, "public"), { + headers: config.extra.headers, dotfiles: "allow", }), }); -- cgit 1.4.1 From 2f6152539c540697290ec73a7c1b50b9f2db88c6 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Tue, 12 Sep 2023 16:54:29 +0200 Subject: Use own logic for static file serving --- bun.lockb | Bin 2386 -> 3923 bytes package.json | 3 ++- src/index.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++++++----- test/index.test.ts | 12 ++++----- 4 files changed, 72 insertions(+), 14 deletions(-) (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index c8b2651..e8b79d3 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 27f95e0..0eb65a3 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "homestead", "module": "src/index.ts", "devDependencies": { + "@types/contains-path": "^1.0.2", "bun-html-live-reload": "^0.1.1", "bun-types": "latest" }, @@ -10,7 +11,7 @@ }, "type": "module", "dependencies": { - "serve-static-bun": "^0.5.3", + "contains-path": "^1.0.0", "toml": "^3.0.0" } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index d8c9bf1..ec1c296 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,75 @@ import path from "node:path"; +import fs, { Stats } from "node:fs"; +import fsp from "node:fs/promises"; +import util from "node:util"; + import { withHtmlLiveReload } from "bun-html-live-reload"; -import serveStatic from "serve-static-bun"; +import containsPath from "contains-path"; import readConfig from "./config"; -const base = Bun.argv.length > 2 ? Bun.argv[Bun.argv.length - 1] : "."; +const base = "../website/"; +const publicDir = path.resolve(base, "public") + path.sep; const config = readConfig(base); +const defaultHeaders = config.extra.headers; + +function getFilename(name: string): string { + return path.join(publicDir, `${name}`); +} + +let files: Map = new Map(); + +function registerFile(pathname: string, absPath: string, stat: Stats): void { + pathname = "/" + (pathname === "." || pathname === "./" ? "" : pathname); + + if (files.get(pathname) !== undefined) { + console.warn("File already registered:", pathname); + } + files.set(pathname, { + filename: absPath, + size: stat.size, + mtime: stat.mtime.toUTCString(), + }); +} + +function walkDirectory(root: string, dir: string) { + const absDir = path.join(root, dir); + for (let pathname of fs.readdirSync(absDir)) { + const relPath = path.join(dir, pathname); + const absPath = path.join(absDir, pathname); + const stat = fs.statSync(absPath); + if (stat.isDirectory()) { + walkDirectory(root, relPath + path.sep); + } else if (stat.isFile()) { + if (pathname === "index.html") { + registerFile(path.dirname(relPath), absPath, stat); + registerFile(path.dirname(relPath) + path.sep, absPath, stat); + } + registerFile(relPath, absPath, stat); + } + } +} + +walkDirectory(publicDir, ""); export default withHtmlLiveReload({ - fetch: serveStatic(path.join(base, "public"), { - headers: config.extra.headers, - dotfiles: "allow", - }), + fetch: async function (request) { + const pathname = new URL(request.url).pathname; + if (files.has(pathname)) { + const file = files.get(pathname); + console.info("filename", file.filename); + return new Response(Bun.file(file.filename), { + headers: defaultHeaders, + status: 200, + }); + } + return new Response(Bun.file(getFilename("404.html")), { + headers: Object.assign({}, defaultHeaders, { + "cache-control": "max-age=5, no-cache", + }), + status: 404, + statusText: "Not Found", + }); + }, }); diff --git a/test/index.test.ts b/test/index.test.ts index 234bd1e..c1f5f64 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -15,14 +15,12 @@ afterAll(function () { server.stop(); }); -test("/status returns 200 OK", async function () { - const res = await fetch(new URL("/status", base)); +test("/ returns 200", async function () { + const res = await fetch(base); expect(res.status).toBe(200); - expect(await res.text()).toBe("OK"); }); -test("/ returns 200 and says Hello world", async function () { - const res = await fetch(base); - expect(res.status).toBe(200); - expect(await res.text()).toBe("Hello world"); +test("/asdf returns 404", async function () { + const res = await fetch(`${base}asdf`); + expect(res.status).toBe(404); }); -- cgit 1.4.1 From 817164e34248c559d4a883bf991e48407111473a Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Tue, 12 Sep 2023 18:09:08 +0200 Subject: Remove unused dependency --- bun.lockb | Bin 3923 -> 2002 bytes package.json | 4 +--- src/index.ts | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index e8b79d3..5ccd483 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 0eb65a3..314e9f9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "homestead", "module": "src/index.ts", "devDependencies": { - "@types/contains-path": "^1.0.2", "bun-html-live-reload": "^0.1.1", "bun-types": "latest" }, @@ -11,7 +10,6 @@ }, "type": "module", "dependencies": { - "contains-path": "^1.0.0", "toml": "^3.0.0" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 1b181a1..8ae1d0d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,6 @@ import fsp from "node:fs/promises"; import util from "node:util"; import { withHtmlLiveReload } from "bun-html-live-reload"; -import containsPath from "contains-path"; import readConfig from "./config"; -- cgit 1.4.1 From 37ee14f5472a9bb4688238fb89bd1d9f2658e66d Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 13 Sep 2023 09:24:11 +0200 Subject: Return precompressed files, if they exist --- bun.lockb | Bin 2002 -> 3084 bytes package.json | 2 ++ src/index.ts | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 67 insertions(+), 8 deletions(-) (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index 5ccd483..2bb84c6 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 314e9f9..7798b26 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "homestead", "module": "src/index.ts", "devDependencies": { + "@types/mime-types": "^2.1.1", "bun-html-live-reload": "^0.1.1", "bun-types": "latest" }, @@ -10,6 +11,7 @@ }, "type": "module", "dependencies": { + "mime-types": "^2.1.35", "toml": "^3.0.0" } } diff --git a/src/index.ts b/src/index.ts index b6d0713..1616df7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,8 @@ import path from "node:path"; import fs, { Stats } from "node:fs"; import fsp from "node:fs/promises"; import { withHtmlLiveReload } from "bun-html-live-reload"; +import mime from "mime-types"; +import type { BunFile } from "bun-types"; import readConfig from "./config"; @@ -13,6 +15,8 @@ const defaultHeaders = config.extra.headers; type File = { filename: string; + handle: BunFile; + relPath: string; headers?: Record; size: number; mtime: Date; @@ -20,7 +24,12 @@ type File = { let files = new Map(); -function registerFile(pathname: string, filename: string, stat: Stats): void { +function registerFile( + path: string, + pathname: string, + filename: string, + stat: Stats, +): void { pathname = "/" + (pathname === "." || pathname === "./" ? "" : pathname); if (files.get(pathname) !== undefined) { @@ -28,6 +37,8 @@ function registerFile(pathname: string, filename: string, stat: Stats): void { } files.set(pathname, { filename, + relPath: "/" + path, + handle: Bun.file(filename), headers: pathname === "/404.html" ? Object.assign({}, defaultHeaders, { "cache-control": "no-cache" }) @@ -46,14 +57,14 @@ function walkDirectory(root: string, dir: string) { if (stat.isDirectory()) { walkDirectory(root, relPath + path.sep); } else if (stat.isFile()) { - if (pathname === "index.html") { - const dir = path.dirname(relPath); - registerFile(dir, absPath, stat); + if (pathname.startsWith("index.html")) { + const dir = relPath.replace("index.html", ""); + registerFile(relPath, dir, absPath, stat); if (dir !== ".") { - registerFile(dir + path.sep, absPath, stat); + registerFile(relPath, dir + path.sep, absPath, stat); } } - registerFile(relPath, absPath, stat); + registerFile(relPath, relPath, absPath, stat); } } } @@ -63,24 +74,41 @@ walkDirectory(publicDir, ""); async function serveFile( file: File | undefined, statusCode: number = 200, + extraHeaders: Record = {}, ): Promise { - if (file && (await fsp.exists(file.filename))) { - return new Response(Bun.file(file.filename), { + if (file && file.handle.exists()) { + return new Response(file.handle, { headers: { "last-modified": file.mtime.toUTCString(), + ...extraHeaders, ...(file.headers || defaultHeaders), }, status: statusCode, }); } else { + // TODO return encoded return serveFile(files.get("/404.html"), 404); } } +async function serveEncodedFile( + file: File | undefined, + statusCode: number = 200, + extraHeaders: Record = {}, +): Promise { + const res = await serveFile(file, statusCode, extraHeaders); + res.headers.delete("content-disposition"); + return res; +} + function parseIfModifiedSinceHeader(header: string | null): number { return header ? new Date(header).getTime() + 999 : 0; } +function getMIME(filename: string): string { + return mime.contentType(path.extname(filename)) || "text/plain"; +} + export default withHtmlLiveReload({ fetch: async function (request) { const pathname = new URL(request.url).pathname; @@ -92,6 +120,35 @@ export default withHtmlLiveReload({ ) { return new Response("", { status: 304 }); } + const encodings = (request.headers.get("accept-encoding") || "") + .split(",") + .map((x) => x.trim().toLowerCase()); + if (encodings.includes("br") && files.has(file.relPath + ".br")) { + console.log( + "Using br encoding for user agent", + request.headers.get("user-agent"), + ); + return serveEncodedFile(files.get(file.relPath + ".br"), 200, { + "content-encoding": "br", + "content-type": getMIME(file.filename), + }); + } else if ( + encodings.includes("zstd") && + files.has(file.relPath + ".zst") + ) { + return serveEncodedFile(files.get(file.relPath + ".zst"), 200, { + "content-encoding": "zstd", + "content-type": getMIME(file.filename), + }); + } else if ( + encodings.includes("gzip") && + files.has(file.relPath + ".gz") + ) { + return serveEncodedFile(files.get(file.relPath + ".gz"), 200, { + "content-encoding": "gzip", + "content-type": getMIME(file.filename), + }); + } } return serveFile(file); }, -- cgit 1.4.1 From c738123d55b8c9418a76afcbfd4480cc90de0e94 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 13 Sep 2023 10:16:53 +0200 Subject: Use bun's builtin mime logic --- bun.lockb | Bin 3084 -> 2002 bytes package.json | 2 -- src/index.ts | 11 +++-------- 3 files changed, 3 insertions(+), 10 deletions(-) (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index 2bb84c6..5af2fa2 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 7798b26..314e9f9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "homestead", "module": "src/index.ts", "devDependencies": { - "@types/mime-types": "^2.1.1", "bun-html-live-reload": "^0.1.1", "bun-types": "latest" }, @@ -11,7 +10,6 @@ }, "type": "module", "dependencies": { - "mime-types": "^2.1.35", "toml": "^3.0.0" } } diff --git a/src/index.ts b/src/index.ts index 1616df7..4382aa4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,6 @@ import path from "node:path"; import fs, { Stats } from "node:fs"; import fsp from "node:fs/promises"; import { withHtmlLiveReload } from "bun-html-live-reload"; -import mime from "mime-types"; import type { BunFile } from "bun-types"; import readConfig from "./config"; @@ -105,10 +104,6 @@ function parseIfModifiedSinceHeader(header: string | null): number { return header ? new Date(header).getTime() + 999 : 0; } -function getMIME(filename: string): string { - return mime.contentType(path.extname(filename)) || "text/plain"; -} - export default withHtmlLiveReload({ fetch: async function (request) { const pathname = new URL(request.url).pathname; @@ -130,7 +125,7 @@ export default withHtmlLiveReload({ ); return serveEncodedFile(files.get(file.relPath + ".br"), 200, { "content-encoding": "br", - "content-type": getMIME(file.filename), + "content-type": file.handle.type, }); } else if ( encodings.includes("zstd") && @@ -138,7 +133,7 @@ export default withHtmlLiveReload({ ) { return serveEncodedFile(files.get(file.relPath + ".zst"), 200, { "content-encoding": "zstd", - "content-type": getMIME(file.filename), + "content-type": file.handle.type, }); } else if ( encodings.includes("gzip") && @@ -146,7 +141,7 @@ export default withHtmlLiveReload({ ) { return serveEncodedFile(files.get(file.relPath + ".gz"), 200, { "content-encoding": "gzip", - "content-type": getMIME(file.filename), + "content-type": file.handle.type, }); } } -- cgit 1.4.1 From 93197c349e19f1e5b1cb5244827eab64aa4ebba4 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 13 Sep 2023 10:19:40 +0200 Subject: Remove defunct live-reload Doesn't work when serving static files --- bun.lockb | Bin 2002 -> 1630 bytes package.json | 1 - src/index.ts | 7 +++---- 3 files changed, 3 insertions(+), 5 deletions(-) (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index 5af2fa2..9d90bd9 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 314e9f9..354305b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "homestead", "module": "src/index.ts", "devDependencies": { - "bun-html-live-reload": "^0.1.1", "bun-types": "latest" }, "peerDependencies": { diff --git a/src/index.ts b/src/index.ts index 4382aa4..69c9b1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,7 @@ import path from "node:path"; import fs, { Stats } from "node:fs"; import fsp from "node:fs/promises"; -import { withHtmlLiveReload } from "bun-html-live-reload"; -import type { BunFile } from "bun-types"; +import type { BunFile, Serve } from "bun"; import readConfig from "./config"; @@ -104,7 +103,7 @@ function parseIfModifiedSinceHeader(header: string | null): number { return header ? new Date(header).getTime() + 999 : 0; } -export default withHtmlLiveReload({ +export default { fetch: async function (request) { const pathname = new URL(request.url).pathname; const file = files.get(pathname); @@ -147,4 +146,4 @@ export default withHtmlLiveReload({ } return serveFile(file); }, -}); +} satisfies Serve; -- cgit 1.4.1 From 627aec8448ca075ea4bf87f85229c67a7374eac0 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 13 Sep 2023 15:55:48 +0200 Subject: Collect metrics for prometheus --- bun.lockb | Bin 1630 -> 2683 bytes package.json | 1 + src/index.ts | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index 9d90bd9..bea9f9d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 354305b..a0b6a73 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "type": "module", "dependencies": { + "bun-prometheus-client": "^0.0.2", "toml": "^3.0.0" } } diff --git a/src/index.ts b/src/index.ts index ff154fa..6699a10 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import path from "node:path"; import fs, { Stats } from "node:fs"; import type { BunFile, Serve } from "bun"; +import prom from "bun-prometheus-client"; import readConfig from "./config"; @@ -22,6 +23,26 @@ type File = { mtime: Date; }; +const collectDefaultMetrics = prom.collectDefaultMetrics; +collectDefaultMetrics({ + labels: { + FLY_APP_NAME: Bun.env.FLY_APP_NAME, + FLY_ALLOC_ID: Bun.env.FLY_ALLOC_ID, + FLY_REGION: Bun.env.FLY_REGION, + }, +}); +const counters = { + requestsByStatus: new prom.Counter({ + name: "requests_by_status", + help: "Number of requests by status code", + labelNames: ["status_code"], + }), + requestsByPath: new prom.Counter({ + name: "requests_by_path", + help: "Number of requests by path", + }), +}; + let files = new Map(); function registerFile( @@ -77,6 +98,7 @@ async function serveFile( extraHeaders: Record = {}, ): Promise { if (file && (await file.handle.exists())) { + counters.requestsByStatus.inc({ status_code: statusCode }); return new Response(file.handle, { headers: { "last-modified": file.mtime.toUTCString(), @@ -86,6 +108,7 @@ async function serveFile( status: statusCode, }); } else { + counters.requestsByStatus.inc({ status_code: 404 }); // TODO return encoded return serveFile(files.get("/404.html"), 404); } @@ -105,15 +128,30 @@ function parseIfModifiedSinceHeader(header: string | null): number { return header ? new Date(header).getTime() + 999 : 0; } +Bun.serve({ + port: 9091, + fetch: async function (request) { + const pathname = new URL(request.url).pathname; + switch (pathname) { + case "/metrics": + return new Response(await prom.register.metrics()); + default: + return new Response("", { status: 404 }); + } + }, +}); + export default { fetch: async function (request) { const pathname = new URL(request.url).pathname; const file = files.get(pathname); + counters.requestsByPath.inc({ path: pathname }); if (file) { if ( parseIfModifiedSinceHeader(request.headers.get("if-modified-since")) >= file?.mtime.getTime() ) { + counters.requestsByStatus.inc({ status_code: 304 }); return new Response("", { status: 304, headers: defaultHeaders }); } const encodings = (request.headers.get("accept-encoding") || "") -- cgit 1.4.1 From 02abf6ebb5ac4979537ee52ccdc93a4f29820cea Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Thu, 14 Sep 2023 11:40:17 +0200 Subject: Build with docker --- .dockerignore | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 60 +++++++++++++++++++++ package.json | 3 ++ src/index.ts | 2 +- 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile (limited to 'package.json') diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f81d56e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,169 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..79533e9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,60 @@ +# syntax = docker/dockerfile:1 + +# Adjust BUN_VERSION as desired +ARG BUN_VERSION=0.8.1 +ARG ZOLA_VERSION=0.17.1 +FROM oven/bun:${BUN_VERSION} as base + +LABEL fly_launch_runtime="Bun" + +# Bun app lives here +WORKDIR /app + +# Set production environment +ENV NODE_ENV="production" + +# Throw-away build stage to reduce size of final image +FROM base as build + +# # Install packages needed to build node modules +# RUN apt-get update -qq && \ +# apt-get install -y build-essential pkg-config python-is-python3 + +# Install node modules +COPY --link bun.lockb package.json ./ +RUN bun install --ci + +# Copy application code +COPY --link src src + +FROM ghcr.io/getzola/zola:v${ZOLA_VERSION} as ssg + +WORKDIR /web + +COPY --link website ./ + +RUN [ "zola", "build", "--force" ] + +FROM alpine:edge as postprocess + +WORKDIR /web + +RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories + +RUN apk add --no-cache prettier@testing make fd brotli gzip zstd + +COPY --from=ssg /web ./ + +RUN make -j4 format compress + +# Final stage for app image +FROM base + +# Copy built application +COPY --from=build /app /app +COPY --from=postprocess /web/ /app/website + +# Start the server by default, this can be overwritten at runtime +EXPOSE 3000 +EXPOSE 9091 +CMD [ "bun", "run", "src/index.ts" ] diff --git a/package.json b/package.json index a0b6a73..f51b68d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,9 @@ { "name": "homestead", "module": "src/index.ts", + "scripts": { + "start": "bun run ." + }, "devDependencies": { "bun-types": "latest" }, diff --git a/src/index.ts b/src/index.ts index 2818682..f968fda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import prom from "bun-prometheus-client"; import readConfig from "./config"; -const base = "../website/"; +const base = "./website/"; const publicDir = path.resolve(base, "public") + path.sep; const config = readConfig(base); -- cgit 1.4.1 From 7bb417f923bdaac08d0d48ef1df6d191e5030bd6 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Thu, 14 Sep 2023 12:07:05 +0200 Subject: Catch errors and log to sentry --- bun.lockb | Bin 2683 -> 7240 bytes package.json | 1 + src/index.ts | 82 +++++++++++++++++++++++++++++++++-------------------------- 3 files changed, 47 insertions(+), 36 deletions(-) (limited to 'package.json') diff --git a/bun.lockb b/bun.lockb index bea9f9d..2608647 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index f51b68d..acb0224 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "type": "module", "dependencies": { + "@sentry/node": "^7.69.0", "bun-prometheus-client": "^0.0.2", "toml": "^3.0.0" } diff --git a/src/index.ts b/src/index.ts index f968fda..d979f43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,13 @@ import path from "node:path"; import fs, { Stats } from "node:fs"; import type { BunFile, Serve } from "bun"; +import * as Sentry from "@sentry/node"; import prom from "bun-prometheus-client"; import readConfig from "./config"; +Sentry.init({}); + const base = "./website/"; const publicDir = path.resolve(base, "public") + path.sep; @@ -148,44 +151,51 @@ console.info( const server = Bun.serve({ fetch: async function (request) { - const pathname = new URL(request.url).pathname; - const file = files.get(pathname); - counters.requestsByPath.inc({ path: pathname }); - if (file) { - if ( - parseIfModifiedSinceHeader(request.headers.get("if-modified-since")) >= - file?.mtime.getTime() - ) { - counters.requestsByStatus.inc({ status_code: 304 }); - return new Response("", { status: 304, headers: defaultHeaders }); - } - const encodings = (request.headers.get("accept-encoding") || "") - .split(",") - .map((x) => x.trim().toLowerCase()); - if (encodings.includes("br") && files.has(file.relPath + ".br")) { - return serveEncodedFile(files.get(file.relPath + ".br"), 200, { - "content-encoding": "br", - "content-type": file.handle.type, - }); - } else if ( - encodings.includes("zstd") && - files.has(file.relPath + ".zst") - ) { - return serveEncodedFile(files.get(file.relPath + ".zst"), 200, { - "content-encoding": "zstd", - "content-type": file.handle.type, - }); - } else if ( - encodings.includes("gzip") && - files.has(file.relPath + ".gz") - ) { - return serveEncodedFile(files.get(file.relPath + ".gz"), 200, { - "content-encoding": "gzip", - "content-type": file.handle.type, - }); + try { + const pathname = new URL(request.url).pathname; + const file = files.get(pathname); + counters.requestsByPath.inc({ path: pathname }); + if (file) { + if ( + parseIfModifiedSinceHeader( + request.headers.get("if-modified-since"), + ) >= file?.mtime.getTime() + ) { + counters.requestsByStatus.inc({ status_code: 304 }); + return new Response("", { status: 304, headers: defaultHeaders }); + } + const encodings = (request.headers.get("accept-encoding") || "") + .split(",") + .map((x) => x.trim().toLowerCase()); + if (encodings.includes("br") && files.has(file.relPath + ".br")) { + return serveEncodedFile(files.get(file.relPath + ".br"), 200, { + "content-encoding": "br", + "content-type": file.handle.type, + }); + } else if ( + encodings.includes("zstd") && + files.has(file.relPath + ".zst") + ) { + return serveEncodedFile(files.get(file.relPath + ".zst"), 200, { + "content-encoding": "zstd", + "content-type": file.handle.type, + }); + } else if ( + encodings.includes("gzip") && + files.has(file.relPath + ".gz") + ) { + return serveEncodedFile(files.get(file.relPath + ".gz"), 200, { + "content-encoding": "gzip", + "content-type": file.handle.type, + }); + } } + return serveFile(file); + } catch (error) { + counters.requestsByStatus.inc({ status_code: 503 }); + Sentry.captureException(error); + return new Response("Something went wrong", { status: 503 }); } - return serveFile(file); }, }); -- cgit 1.4.1