From 442158bc05501985970590c42749bb9260923602 Mon Sep 17 00:00:00 2001 From: dankmeme01 <42031238+dankmeme01@users.noreply.github.com> Date: Mon, 14 Sep 2026 11:32:50 +0200 Subject: [PATCH 1/5] add docker support --- .dockerignore | 14 +++++++++++ .github/workflows/build.yml | 43 +++++++++++++++++++++++++++++++++ Dockerfile | 25 +++++++++++++++++++ src/hooks.server.ts | 10 ++++---- src/lib/api/index-repository.ts | 7 ++---- src/lib/server/redis.ts | 7 ++---- src/routes/+layout.svelte | 6 ++--- 7 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/build.yml create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..821a40d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +node_modules +.svelte-kit +build +.git +.gitignore +.env +.env.* +!.env.example +.vscode +.idea +Dockerfile +.dockerignore +pnpm-lock.yaml +pnpm-workspace.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c30a98b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +name: Build Docker image + +on: + push: + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v6 + with: + images: ghcr.io/geode-sdk/website + tags: | + # use 'latest' for main branch, and use branch name for other branches + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=ref,event=branch,enable=${{ github.ref != 'refs/heads/main' }} + + - name: Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build and push + uses: docker/build-push-action@v7 + with: + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e67a694 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +# build the app first +FROM node:26-slim AS builder +WORKDIR /app +COPY package.json package-lock.json .npmrc ./ +RUN npm ci +COPY . . +RUN npm run build && npm prune --production + +# build only deps needed for runtime, that will be copied to final stage +FROM node:26-slim AS prod-deps +WORKDIR /app +COPY package.json package-lock.json .npmrc ./ +RUN npm ci --omit=dev && npm cache clean --force + +# runner stage +FROM node:26-slim AS runner +WORKDIR /app +ENV NODE_ENV=production HOST=0.0.0.0 PORT=3000 +COPY --from=prod-deps /app/node_modules ./node_modules +COPY --from=builder /app/build ./build +COPY package.json . + +USER node +EXPOSE 3000 +CMD ["node", "build"] diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6589dab..f8e1812 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,6 +1,6 @@ import type { HandleFetch } from "@sveltejs/kit"; -import * as publicEnv from "$env/static/public"; -import * as privateEnv from "$env/static/private"; +import { env as publicEnv } from "$env/dynamic/public"; +import { env as privateEnv } from "$env/dynamic/private"; interface PrivateSchema { PRIVATE_ENDPOINT_ENABLED: string; @@ -39,10 +39,10 @@ function checkPublic(obj: unknown): obj is PublicSchema { return true; } -const validPrivate = checkPrivate(privateEnv) ? privateEnv : null; -const validPublic = checkPublic(publicEnv) ? publicEnv : null; - export const handleFetch: HandleFetch = async ({ request, fetch }) => { + const validPrivate = checkPrivate(privateEnv) ? privateEnv : null; + const validPublic = checkPublic(publicEnv) ? publicEnv : null; + if (!validPrivate || !validPublic) { return fetch(request); } diff --git a/src/lib/api/index-repository.ts b/src/lib/api/index-repository.ts index 7fe0436..e038df6 100644 --- a/src/lib/api/index-repository.ts +++ b/src/lib/api/index-repository.ts @@ -1,4 +1,4 @@ -import * as publicEnv from "$env/static/public"; +import { env as publicEnv } from "$env/dynamic/public"; import type { ServerDeveloper, ServerDeveloperProfile, ServerTag } from "./models/base"; import type { ServerMod, ServerModDeprecation, ServerSimpleMod } from "./models/mod.js"; @@ -14,10 +14,7 @@ import type { ServerStats } from "./models/stats"; import type { Cookies } from "@sveltejs/kit"; import { setCookieTokens } from "$lib/api/tokens"; -const BASE_URL = - "PUBLIC_API_ENDPOINT" in publicEnv && typeof publicEnv.PUBLIC_API_ENDPOINT == "string" - ? publicEnv.PUBLIC_API_ENDPOINT - : "https://api.geode-sdk.org"; +const BASE_URL = publicEnv.PUBLIC_API_ENDPOINT || "https://api.geode-sdk.org"; export interface Paginated { data: T[]; diff --git a/src/lib/server/redis.ts b/src/lib/server/redis.ts index a2c0210..8dcd64f 100644 --- a/src/lib/server/redis.ts +++ b/src/lib/server/redis.ts @@ -1,10 +1,7 @@ -import * as privateEnv from "$env/static/private"; +import { env as privateEnv } from "$env/dynamic/private"; import { createClient } from "redis"; -const redisUrl = - "PRIVATE_REDIS_URL" in privateEnv && typeof privateEnv.PRIVATE_REDIS_URL == "string" - ? privateEnv.PRIVATE_REDIS_URL - : null; +const redisUrl = privateEnv.PRIVATE_REDIS_URL || null; export const redis = redisUrl ? createClient({ url: redisUrl }) : null; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 56aeca7..fe51571 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -11,6 +11,7 @@ import { onMount } from "svelte"; import type { LayoutData } from "./$types"; import { setUserContext } from "$lib/context/user"; + import { env as publicEnv } from "$env/dynamic/public"; interface Props { data: LayoutData; @@ -21,10 +22,7 @@ let { data, children, nav }: Props = $props(); setUserContext(() => data.loggedInUser); - import * as publicEnv from "$env/static/public"; - const GID = "PUBLIC_GTAG_ID" in publicEnv && typeof publicEnv.PUBLIC_GTAG_ID == "string" - ? publicEnv.PUBLIC_GTAG_ID - : ""; + const GID = publicEnv.PUBLIC_GTAG_ID ?? ""; if (GID !== "") { onMount(() => { From d686ca6dd11ec151fa45f5e08fc2f74ad26dc9dc Mon Sep 17 00:00:00 2001 From: dankmeme01 <42031238+dankmeme01@users.noreply.github.com> Date: Mon, 14 Sep 2026 11:42:21 +0200 Subject: [PATCH 2/5] fix package lock, add caching to workflow, remove dead step in dockerfile --- .github/workflows/build.yml | 4 ++- Dockerfile | 2 +- package-lock.json | 58 ++++++++++++++++++------------------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c30a98b..899686e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,4 +40,6 @@ jobs: with: push: true tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max,compression=zstd,compression-level=12 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e67a694..f16ebbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY package.json package-lock.json .npmrc ./ RUN npm ci COPY . . -RUN npm run build && npm prune --production +RUN npm run build # build only deps needed for runtime, that will be copied to final stage FROM node:26-slim AS prod-deps diff --git a/package-lock.json b/package-lock.json index 1054315..b0fa070 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,23 +11,23 @@ "@iconify/svelte": "^5.1.0", "@svelte-put/clickoutside": "^4.0.0", "@sveltejs/adapter-auto": "^7.0.0", - "@sveltejs/adapter-node": "^5.5", + "@sveltejs/adapter-node": "^5.5.4", "@sveltejs/enhanced-img": "^0.10", - "@sveltejs/kit": "^2.57", - "@sveltejs/vite-plugin-svelte": "^7.0", + "@sveltejs/kit": "^2.58.0", + "@sveltejs/vite-plugin-svelte": "^7.0.0", "@tailwindcss/vite": "^4.2.4", "clsx": "^2.1.1", "dayjs": "^1.11.11", "redis": "^5.9.0", "strip-indent": "^4.0.0", - "svelte": "^5.0.0", + "svelte": "^5.55.5", "svelte-check": "^4.0.0", "svelte-exmarkdown": "^5.0.2", "svelte-highlight": "^7.6.0", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.4", "typescript": "^5.5.0", - "vite": "^8.0" + "vite": "^8.0.10" }, "devDependencies": { "@types/node": "^24.10.0", @@ -1478,9 +1478,9 @@ } }, "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.8.tgz", - "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", + "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -1529,17 +1529,17 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.57.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.57.1.tgz", - "integrity": "sha512-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw==", + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.65.1.tgz", + "integrity": "sha512-Sa1rFYYqBB+zv3rIxAg/CsFskR/x4aj5BY/hvLxBd9r/mqbipxM945As1K3PqsDicJAyekPR0BlWoVIiw2OHYg==", "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", - "@sveltejs/acorn-typescript": "^1.0.5", + "@sveltejs/acorn-typescript": "^1.0.9", "@types/cookie": "^0.6.0", - "acorn": "^8.14.1", + "acorn": "^8.16.0", "cookie": "^0.6.0", - "devalue": "^5.6.4", + "devalue": "^5.8.1", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", @@ -1947,9 +1947,9 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2118,9 +2118,9 @@ } }, "node_modules/devalue": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.7.1.tgz", - "integrity": "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", "license": "MIT" }, "node_modules/devlop": { @@ -2168,9 +2168,9 @@ "license": "MIT" }, "node_modules/esrap": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.5.tgz", - "integrity": "sha512-/yLB1538mag+dn0wsePTe8C0rDIjUOaJpMs2McodSzmM2msWcZsBSdRtg6HOBt0A/r82BN+Md3pgwSc/uWt2Ig==", + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.11.tgz", + "integrity": "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -3925,23 +3925,23 @@ } }, "node_modules/svelte": { - "version": "5.55.4", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.4.tgz", - "integrity": "sha512-q8DFohk6vUswSng95IZb9nzWJnbINZsK7OiM1snAa3qCjJBL0ZQpvMyAaVXjUukdM75J/m8UE8xwqat8Ors/zQ==", + "version": "5.56.3", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.3.tgz", + "integrity": "sha512-w7JvrM5IFl5cmfbY0TLik9o7mjRUJmRMhOR51tBPu708Gr/MjbGs7VnJnr/B0CaXeI4vtnOh7RKxDr0cwhMdDA==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", + "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", - "devalue": "^5.6.4", + "devalue": "^5.8.1", "esm-env": "^1.2.1", - "esrap": "^2.2.4", + "esrap": "^2.2.11", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", From d13b407bcb0a29887d690a89c103f800120b76d9 Mon Sep 17 00:00:00 2001 From: dankmeme01 <42031238+dankmeme01@users.noreply.github.com> Date: Mon, 14 Sep 2026 11:45:08 +0200 Subject: [PATCH 3/5] add action name and update readme --- .github/workflows/build.yml | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 899686e..d81f7b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,7 @@ permissions: jobs: docker: + name: Build and push Docker image runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/README.md b/README.md index 39596b6..a9640ce 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ You can preview the production build with `npm run preview`. ## Configuration -The site uses the following (optional) static environment variables: +The site uses the following (optional) dynamic environment variables: - `PUBLIC_API_ENDPOINT`: Base API endpoint for all requests. Current default is "https://api.geode-sdk.org". From 4c2e0fe0baf93e6859142274a9c3ae96f884a660 Mon Sep 17 00:00:00 2001 From: dankmeme01 <42031238+dankmeme01@users.noreply.github.com> Date: Mon, 14 Sep 2026 12:21:37 +0200 Subject: [PATCH 4/5] use alpine --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index f16ebbf..888b33a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # build the app first -FROM node:26-slim AS builder +FROM node:26-alpine AS builder WORKDIR /app COPY package.json package-lock.json .npmrc ./ RUN npm ci @@ -7,13 +7,13 @@ COPY . . RUN npm run build # build only deps needed for runtime, that will be copied to final stage -FROM node:26-slim AS prod-deps +FROM node:26-alpine AS prod-deps WORKDIR /app COPY package.json package-lock.json .npmrc ./ RUN npm ci --omit=dev && npm cache clean --force # runner stage -FROM node:26-slim AS runner +FROM node:26-alpine AS runner WORKDIR /app ENV NODE_ENV=production HOST=0.0.0.0 PORT=3000 COPY --from=prod-deps /app/node_modules ./node_modules From 04d27815277e57b5b382998e5fd890719af6f66a Mon Sep 17 00:00:00 2001 From: dankmeme01 <42031238+dankmeme01@users.noreply.github.com> Date: Mon, 14 Sep 2026 12:52:08 +0200 Subject: [PATCH 5/5] fixes --- .dockerignore | 3 ++- src/lib/api/index-repository.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index 821a40d..d05f365 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,10 +5,11 @@ build .gitignore .env .env.* -!.env.example .vscode .idea Dockerfile .dockerignore +.prettierrc +.prettierignore pnpm-lock.yaml pnpm-workspace.yaml diff --git a/src/lib/api/index-repository.ts b/src/lib/api/index-repository.ts index e038df6..65eeb86 100644 --- a/src/lib/api/index-repository.ts +++ b/src/lib/api/index-repository.ts @@ -1,4 +1,4 @@ -import { env as publicEnv } from "$env/dynamic/public"; +import { env as publicEnv } from "$env/dynamic/public"; import type { ServerDeveloper, ServerDeveloperProfile, ServerTag } from "./models/base"; import type { ServerMod, ServerModDeprecation, ServerSimpleMod } from "./models/mod.js";