refactor: move environment variables to runtime for secure containerized builds
Some checks failed
CI / build-and-deploy (push) Failing after 47s

Change from $env/static/private to $env/dynamic/private for all
environment variables. This allows building in CI without embedding
secrets in build artifacts, while keeping secrets secure on the server
at runtime.

Changes:
- Refactor auth configuration to use dynamic env vars
- Move database connection string to runtime
- Update image API routes to read IMAGE_DIR at runtime
- Add .env.example for documentation

This enables the containerized build workflow to succeed without
requiring a .env file during build, as secrets are only needed when
the application starts on the server.
This commit is contained in:
2025-12-09 11:35:12 +01:00
parent ffb47f3826
commit f40dfd1774
8 changed files with 44 additions and 21 deletions

24
.env.example Normal file
View File

@@ -0,0 +1,24 @@
# Database Configuration
MONGO_URL="mongodb://user:password@host:port/database?authSource=admin"
# Authentication Secrets (runtime only - not embedded in build)
AUTHENTIK_ID="your-authentik-client-id"
AUTHENTIK_SECRET="your-authentik-client-secret"
# Static Configuration (embedded in build - OK to be public)
AUTHENTIK_ISSUER="https://sso.example.com/application/o/your-app/"
# File Storage
IMAGE_DIR="/path/to/static/files"
# Optional: Development Settings
# DEV_DISABLE_AUTH="true"
# ORIGIN="http://127.0.0.1:3000"
# Optional: Additional Configuration
# BEARER_TOKEN="your-bearer-token"
# COOKIE_SECRET="your-cookie-secret"
# PEPPER="your-pepper-value"
# ALLOW_REGISTRATION="1"
# AUTH_SECRET="your-auth-secret"
# USDA_API_KEY="your-usda-api-key"

View File

@@ -1,13 +1,13 @@
import { SvelteKitAuth } from "@auth/sveltekit"
import Authentik from "@auth/core/providers/authentik"
import { AUTHENTIK_ID, AUTHENTIK_SECRET, AUTHENTIK_ISSUER } from "$env/static/private";
import { env } from "$env/dynamic/private";
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Authentik({
clientId: AUTHENTIK_ID,
clientSecret: AUTHENTIK_SECRET,
issuer: AUTHENTIK_ISSUER,
clientId: env.AUTHENTIK_ID,
clientSecret: env.AUTHENTIK_SECRET,
issuer: env.AUTHENTIK_ISSUER,
})],
callbacks: {
// this feels like an extremely hacky way to get nickname and groups into the session object

View File

@@ -3,7 +3,6 @@ import { redirect } from "@sveltejs/kit"
import { error } from "@sveltejs/kit"
import { SvelteKitAuth } from "@auth/sveltekit"
import Authentik from "@auth/core/providers/authentik"
import { AUTHENTIK_ID, AUTHENTIK_SECRET, AUTHENTIK_ISSUER } from "$env/static/private";
import { sequence } from "@sveltejs/kit/hooks"
import * as auth from "./auth"
import { initializeScheduler } from "./lib/server/scheduler"

View File

@@ -3,7 +3,7 @@ import { error, json } from '@sveltejs/kit';
import { writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import { randomUUID } from 'crypto';
import { IMAGE_DIR } from '$env/static/private';
import { env } from '$env/dynamic/private';
export const POST: RequestHandler = async ({ request, locals }) => {
const auth = await locals.auth();
@@ -30,13 +30,13 @@ export const POST: RequestHandler = async ({ request, locals }) => {
const extension = image.type.split('/')[1];
const filename = `${randomUUID()}.${extension}`;
if (!IMAGE_DIR) {
if (!env.IMAGE_DIR) {
throw error(500, 'IMAGE_DIR environment variable not configured');
}
// Ensure cospend directory exists in IMAGE_DIR
const uploadsDir = join(IMAGE_DIR, 'cospend');
const uploadsDir = join(env.IMAGE_DIR, 'cospend');
try {
mkdirSync(uploadsDir, { recursive: true });
} catch (err) {

View File

@@ -1,7 +1,7 @@
import path from 'path'
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { IMAGE_DIR } from '$env/static/private'
import { env } from '$env/dynamic/private'
import sharp from 'sharp';
export const POST = (async ({ request, locals}) => {
@@ -20,21 +20,21 @@ export const POST = (async ({ request, locals}) => {
//}
await sharp(full_res)
.toFormat('webp')
.toFile(path.join(IMAGE_DIR,
.toFile(path.join(env.IMAGE_DIR,
"rezepte",
"full",
data.name + ".webp"))
await sharp(full_res)
.resize({ width: 800})
.toFormat('webp')
.toFile(path.join(IMAGE_DIR,
.toFile(path.join(env.IMAGE_DIR,
"rezepte",
"thumb",
data.name + ".webp"))
await sharp(full_res)
.resize({ width: 20})
.toFormat('webp')
.toFile(path.join(IMAGE_DIR,
.toFile(path.join(env.IMAGE_DIR,
"rezepte",
"placeholder",
data.name + ".webp"))

View File

@@ -1,6 +1,6 @@
import path from 'path'
import type { RequestHandler } from '@sveltejs/kit';
import { IMAGE_DIR } from '$env/static/private'
import { env } from '$env/dynamic/private'
import { unlink } from 'node:fs';
import { error } from '@sveltejs/kit';
@@ -10,7 +10,7 @@ export const POST = (async ({ request, locals}) => {
if(!auth) throw error(401, "You need to be logged in")
[ "full", "thumb", "placeholder"].forEach((folder) => {
unlink(path.join(IMAGE_DIR, "rezepte", folder, data.name + ".webp"), (e) => {
unlink(path.join(env.IMAGE_DIR, "rezepte", folder, data.name + ".webp"), (e) => {
if(e) error(404, "could not delete: " + folder + "/" + data.name + ".webp" + e)
})
})

View File

@@ -1,6 +1,6 @@
import path from 'path'
import type { RequestHandler } from '@sveltejs/kit';
import { IMAGE_DIR } from '$env/static/private'
import { env } from '$env/dynamic/private'
import { rename } from 'node:fs';
import { error } from '@sveltejs/kit';
@@ -10,8 +10,8 @@ export const POST = (async ({ request, locals}) => {
if(!auth ) throw error(401, "need to be logged in")
[ "full", "thumb", "placeholder"].forEach((folder) => {
const old_path = path.join(IMAGE_DIR, "rezepte", folder, data.old_name + ".webp")
rename(old_path, path.join(IMAGE_DIR, "rezepte", folder, data.new_name + ".webp"), (e) => {
const old_path = path.join(env.IMAGE_DIR, "rezepte", folder, data.old_name + ".webp")
rename(old_path, path.join(env.IMAGE_DIR, "rezepte", folder, data.new_name + ".webp"), (e) => {
console.log(e)
if(e) throw error(500, "could not mv: " + old_path)
})

View File

@@ -1,5 +1,5 @@
import mongoose from 'mongoose';
import { MONGO_URL } from '$env/static/private';
import { env } from '$env/dynamic/private';
let isConnected = false;
@@ -17,7 +17,7 @@ export const dbConnect = async () => {
socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity
};
const connection = await mongoose.connect(MONGO_URL ?? '', options);
const connection = await mongoose.connect(env.MONGO_URL ?? '', options);
isConnected = true;
console.log('MongoDB connected with persistent connection');