diff --git a/Dockerfile b/Dockerfile index 42c1cb3..6f42ec6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,14 @@ -FROM oven/bun:1.3-alpine -LABEL org.opencontainers.image.source=https://github.com/mpoc/sleep-tracker +FROM oven/bun:1.3-alpine AS build WORKDIR /usr/src/sleep-tracker COPY package.json bun.lock ./ RUN bun install --frozen-lockfile --production COPY . . +RUN bun build --compile --asset-naming="[name].[ext]" src/index.ts ./src/static/*.png ./src/static/*.ico ./src/static/*.webmanifest --outfile server + +FROM alpine +RUN apk add --no-cache libgcc libstdc++ +LABEL org.opencontainers.image.source=https://github.com/mpoc/sleep-tracker +WORKDIR /usr/src/sleep-tracker +COPY --from=build /usr/src/sleep-tracker/server ./server EXPOSE 8000 -ENTRYPOINT ["bun", "src/index.ts"] +ENTRYPOINT ["./server"] diff --git a/bun.lock b/bun.lock index 947f4e9..d0dd97f 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,6 @@ "name": "sleep-tracker", "dependencies": { "@elysiajs/bearer": "^1.4.3", - "@elysiajs/static": "^1.4.7", "@openrouter/ai-sdk-provider": "^2.2.3", "ai": "^6.0.105", "croner": "^10.0.1", @@ -74,8 +73,6 @@ "@elysiajs/bearer": ["@elysiajs/bearer@1.4.3", "", { "peerDependencies": { "elysia": ">= 1.4.3" } }, "sha512-UWJ94jGGOzSlD3CCspC11/vFGKwy6RI9QvaZVPzlSu1Wxp/pKmOhKA+R2ppfbluMHXfxcc2xgK3x4+uuCML7GA=="], - "@elysiajs/static": ["@elysiajs/static@1.4.7", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Go4kIXZ0G3iWfkAld07HmLglqIDMVXdyRKBQK/sVEjtpDdjHNb+rUIje73aDTWpZYg4PEVHUpi9v4AlNEwrQug=="], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.2.3", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-NovC+BaCfEeJwhToDrs8JeDYXXlJdEyz7lcxkjtyePSE4eoAKik872SyDK0MzXKcz8MRkv7XlNhPI6zz4TQp0g=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], diff --git a/package.json b/package.json index 8303598..ef1b0bb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "sleep-tracker", "license": "Apache-2.0", "scripts": { - "build": "bun build --target=bun --production --outdir=dist --sourcemap ./src/index.ts ./src/static/*", + "build": "bun build --compile --asset-naming='[name].[ext]' src/index.ts ./src/static/*.png ./src/static/*.ico ./src/static/*.webmanifest --outfile server", "start": "bun src/index.ts", "dev": "bun --watch src/index.ts", "format": "biome format --write ." @@ -22,7 +22,6 @@ }, "dependencies": { "@elysiajs/bearer": "^1.4.3", - "@elysiajs/static": "^1.4.7", "@openrouter/ai-sdk-provider": "^2.2.3", "ai": "^6.0.105", "croner": "^10.0.1", diff --git a/src/checkReminderLoop.ts b/src/checkReminderLoop.ts index 47d182b..40e7bfa 100644 --- a/src/checkReminderLoop.ts +++ b/src/checkReminderLoop.ts @@ -36,6 +36,8 @@ const checkReminderNotification = async () => { }; export const checkReminderLoop = () => { - checkReminderNotification(); + checkReminderNotification().catch((e) => + console.error("checkReminderNotification failed:", e) + ); setTimeout(checkReminderLoop, ms(REMINDER_CHECK_INTERVAL)); }; diff --git a/src/index.ts b/src/index.ts index 1ce8cf0..522880e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ import "./ensureDataDir"; import { bearer } from "@elysiajs/bearer"; -import { staticPlugin } from "@elysiajs/static"; import { Elysia } from "elysia"; import logixlysia from "logixlysia"; +import swJs from "./static/sw.js" with { type: "file" }; import ms from "ms"; import { startAiNotificationCron } from "./aiNotifications"; import { checkReminderLoop } from "./checkReminderLoop"; @@ -93,7 +93,24 @@ new Elysia() .get("/notifications", sleepReactHtml) .get("/notification-feedback", sleepReactHtml) .get("/", sleepReactHtml) - .use(staticPlugin({ assets: "./src/static/", prefix: "/" })) + .get("/*", async ({ params, set }) => { + const name = params["*"]; + for (const blob of Bun.embeddedFiles) { + if (blob.name === name) { + return new Response(blob); + } + } + const embeddedByImport: Record = { "sw.js": swJs }; + if (name in embeddedByImport) { + return new Response(Bun.file(embeddedByImport[name])); + } + const file = Bun.file(`./src/static/${name}`); + if (await file.exists()) { + return new Response(file); + } + set.status = 404; + return "Not found"; + }) .listen(PORT); checkReminderLoop(); diff --git a/src/views/js/frontend.tsx b/src/views/js/frontend.tsx index 25c95a4..2bfac91 100644 --- a/src/views/js/frontend.tsx +++ b/src/views/js/frontend.tsx @@ -8,6 +8,16 @@ if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js"); } +for (const [rel, href] of [ + ["manifest", "/manifest.webmanifest"], + ["apple-touch-icon", "/apple-touch-icon.png"], +] as const) { + const link = document.createElement("link"); + link.rel = rel; + link.href = href; + document.head.appendChild(link); +} + const getPage = () => { if (window.location.pathname === "/notifications") { return ; diff --git a/src/views/sleepReact.html b/src/views/sleepReact.html index 1cbd16a..cb17a1b 100644 --- a/src/views/sleepReact.html +++ b/src/views/sleepReact.html @@ -12,9 +12,7 @@ integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous" > - - Sleep