From 65ae7675ba994bebfb16038147cec2bf608dcb0f Mon Sep 17 00:00:00 2001 From: NeilClack Date: Thu, 16 Jul 2026 22:01:36 -0500 Subject: [PATCH] Complete docker build in preparation for app-runner deployment --- .dockerignore | 8 ++ .env.example | 5 +- Dockerfile | 53 ++++++++++++ deploy.md | 12 --- ...talocean-database.crt => digital-ocean.crt | 0 nixos/module.nix | 81 ------------------- package-lock.json | 3 + package.json | 5 +- src/lib/auth.ts | 8 +- src/lib/db.ts | 13 +-- vite.config.ts | 9 +++ 11 files changed, 88 insertions(+), 109 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile delete mode 100644 deploy.md rename digitalocean-database.crt => digital-ocean.crt (100%) delete mode 100644 nixos/module.nix diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..be97dc3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules +.svelte-kit +build +.git +.env +.env.* +npm-debug.log* +.DS_Store \ No newline at end of file diff --git a/.env.example b/.env.example index 99dc697..9fe22a7 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ DATABASE_URL= BETTER_AUTH_SECRET= BETTER_AUTH_URL= -GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_ID=6 GOOGLE_CLIENT_SECRET= +RESEND_API_KEY= +EMAIL_FROM= +RESEND_TEMPLATE_ID= \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..94784d5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# --- Build stage --- +FROM node:24-slim AS build +WORKDIR /app + +# Placeholder values ONLY so `vite build` doesn't crash on $env/static/* +# imports during the build step. These are NOT the real runtime values — +# App Platform injects the real ones as env vars when the container runs. +ARG DATABASE_URL=postgres://placeholder:placeholder@localhost:5432/placeholder +ARG ORIGIN=http://localhost:3000 +ARG BETTER_AUTH_URL=http://localhost:3000 +ARG BETTER_AUTH_BASE_URL=http://localhost:3000 +ARG BETTER_AUTH_SECRET=placeholder-build-time-secret-not-used-at-runtime +ENV DATABASE_URL=$DATABASE_URL +ENV ORIGIN=$ORIGIN +ENV BETTER_AUTH_URL=$BETTER_AUTH_URL +ENV BETTER_AUTH_BASE_URL=$BETTER_AUTH_BASE_URL +ENV BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET + +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# Drop devDependencies now that the build is done +RUN npm prune --omit=dev + +# --- Runtime stage --- +FROM node:24-slim AS runtime +WORKDIR /app +ENV NODE_ENV=production +ENV PORT=3000 + +# Install the DigitalOcean managed DB CA cert into the system trust store. +# `ca-certificates` provides update-ca-certificates; certs placed in +# /usr/local/share/ca-certificates/ must end in .crt to be picked up. +COPY digital-ocean.crt /usr/local/share/ca-certificates/digital-ocean.crt +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ + && update-ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Node still doesn't read the system trust store unless --use-system-ca is +# passed explicitly (available on 22.19+/24.6+, but off by default even here). +# NODE_EXTRA_CA_CERTS is simpler and works regardless of Node version. +ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/digital-ocean.crt + +COPY --from=build /app/build ./build +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/package.json ./package.json + +EXPOSE 3000 +CMD ["node", "build/index.js"] \ No newline at end of file diff --git a/deploy.md b/deploy.md deleted file mode 100644 index 0c50fcd..0000000 --- a/deploy.md +++ /dev/null @@ -1,12 +0,0 @@ -Initial Setup: -1. Add the portal flake input to Webserver-Infra/flake.nix with a Forgejo access token. -- Does this need to be in just the inputs, or the outputs as well? I'm still very much learning nix and these things get confusing. -2. Add services.portal.enable = true; to Webserver-Infra/configuration.nix -3. Remove the existing users.users.portal and groups because they exist in this flake now -4. Clone the repo on the server, place the .env, build, then run nixos-rebuild switch. This is mainly to start the service, I presume. The build is just so that the service has something to - start. - -To update: -1. git pull -2. Apply shell-instance environment variables for placeholder values to then run `npm ci && npm run build && npm ci --omit=dev` -3. Restart the systemctl service (portal.service) to pickup the changes because the service is in charge of the `node build` command. diff --git a/digitalocean-database.crt b/digital-ocean.crt similarity index 100% rename from digitalocean-database.crt rename to digital-ocean.crt diff --git a/nixos/module.nix b/nixos/module.nix deleted file mode 100644 index 5978065..0000000 --- a/nixos/module.nix +++ /dev/null @@ -1,81 +0,0 @@ -{ config, lib, pkgs, ... }: - -let - cfg = config.services.portal; -in -{ - options.services.portal = { - enable = lib.mkEnableOption "RhemaWave Portal"; - - directory = lib.mkOption { - type = lib.types.path; - default = "/mnt/volume_nyc3_01/Portal.RhemaWaveTech.com"; - description = "Path to the Portal git clone."; - }; - - envFile = lib.mkOption { - type = lib.types.path; - default = "${cfg.directory}/.env"; - defaultText = lib.literalExpression ''"''${cfg.directory}/.env"''; - description = "Path to the .env file containing secrets."; - }; - - port = lib.mkOption { - type = lib.types.port; - default = 3001; - description = "Port the Portal listens on."; - }; - - host = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; - description = "Host address the Portal binds to."; - }; - }; - - config = lib.mkIf cfg.enable { - users.users.portal = { - isSystemUser = true; - group = "portal"; - home = cfg.directory; - }; - - users.groups.portal = { }; - - systemd.services.portal = { - description = "RhemaWave Portal"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - - serviceConfig = { - Type = "simple"; - User = "portal"; - Group = "portal"; - WorkingDirectory = cfg.directory; - EnvironmentFile = cfg.envFile; - ExecStart = "${pkgs.nodejs_22}/bin/node build"; - Restart = "on-failure"; - RestartSec = 5; - }; - - environment = { - HOST = cfg.host; - PORT = toString cfg.port; - DO_CA_CERTIFICATE = "/var/lib/portal/do-ca-certificate.crt"; - NODE_ENV = "production"; - }; - }; - - security.sudo.extraRules = [ - { - users = [ "forgejo-runner" ]; - commands = [ - { - command = "/run/current-system/sw/bin/systemctl restart portal"; - options = [ "NOPASSWD" ]; - } - ]; - } - ]; - }; -} diff --git a/package-lock.json b/package-lock.json index f394660..770a0fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,9 @@ "vite": "^7.2.6", "vitest": "^4.0.15", "vitest-browser-svelte": "^2.0.1" + }, + "engines": { + "node": ">=24" } }, "node_modules/@better-auth/core": { diff --git a/package.json b/package.json index 9469967..bb33188 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "private": true, "version": "0.0.1", "type": "module", + "engines": { + "node": ">=24" + }, "scripts": { "dev": "vite dev", "build": "vite build", @@ -57,4 +60,4 @@ "dotenv": "^17.2.3", "pg": "^8.16.3" } -} +} \ No newline at end of file diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 1af3605..b646e9b 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -3,21 +3,23 @@ import { prismaAdapter } from "better-auth/adapters/prisma"; import { prisma } from "$lib/db"; import { sveltekitCookies } from "better-auth/svelte-kit"; import { getRequestEvent } from "$app/server"; +import { env } from "$env/dynamic/private"; export const auth = betterAuth({ - baseURL: process.env.BETTER_AUTH_URL, + baseURL: env.BETTER_AUTH_URL, database: prismaAdapter(prisma, { provider: "postgresql", }), + secret: env.BETTER_AUTH_SECRET, emailAndPassword: { enabled: true, }, socialProviders: { google: { prompt: "select_account", - clientId: process.env.GOOGLE_CLIENT_ID as string, - clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, + clientId: env.GOOGLE_CLIENT_ID as string, + clientSecret: env.GOOGLE_CLIENT_SECRET, } }, plugins: [sveltekitCookies(getRequestEvent)], diff --git a/src/lib/db.ts b/src/lib/db.ts index d33a2da..10cd777 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,23 +1,14 @@ -import 'dotenv/config'; -import fs from 'fs'; import pg from 'pg'; import { PrismaPg } from '@prisma/adapter-pg'; import { PrismaClient } from '$lib/generated/prisma/client'; +import { env } from '$env/dynamic/private'; let _prisma: PrismaClient | undefined; function getPrisma(): PrismaClient { if (!_prisma) { - const ca_cert = process.env.DO_CA_CERTIFICATE ?? ''; - const pool = new pg.Pool({ - connectionString: process.env.DATABASE_URL, - ssl: ca_cert - ? { - rejectUnauthorized: true, - ca: fs.readFileSync(ca_cert).toString() - } - : undefined + connectionString: env.DATABASE_URL }); const adapter = new PrismaPg(pool); diff --git a/vite.config.ts b/vite.config.ts index 5a478a5..04f79b4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,15 @@ import { sveltekit } from '@sveltejs/kit/vite'; export default defineConfig({ plugins: [tailwindcss(), sveltekit()], + build: { + rollupOptions: { + external: [ + '@prisma/client/runtime/query_compiler_bg.postgresql.mjs', + '@prisma/client/runtime/query_compiler_bg.postgresql.wasm-base64.mjs' + ] + } + }, + test: { expect: { requireAssertions: true },