refactor: move environment variables to runtime for secure containerized builds
Some checks failed
CI / build-and-deploy (push) Failing after 47s
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:
24
.env.example
Normal file
24
.env.example
Normal 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"
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { SvelteKitAuth } from "@auth/sveltekit"
|
import { SvelteKitAuth } from "@auth/sveltekit"
|
||||||
import Authentik from "@auth/core/providers/authentik"
|
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({
|
export const { handle, signIn, signOut } = SvelteKitAuth({
|
||||||
providers: [
|
providers: [
|
||||||
Authentik({
|
Authentik({
|
||||||
clientId: AUTHENTIK_ID,
|
clientId: env.AUTHENTIK_ID,
|
||||||
clientSecret: AUTHENTIK_SECRET,
|
clientSecret: env.AUTHENTIK_SECRET,
|
||||||
issuer: AUTHENTIK_ISSUER,
|
issuer: env.AUTHENTIK_ISSUER,
|
||||||
})],
|
})],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
// this feels like an extremely hacky way to get nickname and groups into the session object
|
// this feels like an extremely hacky way to get nickname and groups into the session object
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { redirect } from "@sveltejs/kit"
|
|||||||
import { error } from "@sveltejs/kit"
|
import { error } from "@sveltejs/kit"
|
||||||
import { SvelteKitAuth } from "@auth/sveltekit"
|
import { SvelteKitAuth } from "@auth/sveltekit"
|
||||||
import Authentik from "@auth/core/providers/authentik"
|
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 { sequence } from "@sveltejs/kit/hooks"
|
||||||
import * as auth from "./auth"
|
import * as auth from "./auth"
|
||||||
import { initializeScheduler } from "./lib/server/scheduler"
|
import { initializeScheduler } from "./lib/server/scheduler"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { error, json } from '@sveltejs/kit';
|
|||||||
import { writeFileSync, mkdirSync } from 'fs';
|
import { writeFileSync, mkdirSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { IMAGE_DIR } from '$env/static/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
export const POST: RequestHandler = async ({ request, locals }) => {
|
export const POST: RequestHandler = async ({ request, locals }) => {
|
||||||
const auth = await locals.auth();
|
const auth = await locals.auth();
|
||||||
@@ -30,13 +30,13 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
|
|
||||||
const extension = image.type.split('/')[1];
|
const extension = image.type.split('/')[1];
|
||||||
const filename = `${randomUUID()}.${extension}`;
|
const filename = `${randomUUID()}.${extension}`;
|
||||||
|
|
||||||
if (!IMAGE_DIR) {
|
if (!env.IMAGE_DIR) {
|
||||||
throw error(500, 'IMAGE_DIR environment variable not configured');
|
throw error(500, 'IMAGE_DIR environment variable not configured');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure cospend directory exists in IMAGE_DIR
|
// Ensure cospend directory exists in IMAGE_DIR
|
||||||
const uploadsDir = join(IMAGE_DIR, 'cospend');
|
const uploadsDir = join(env.IMAGE_DIR, 'cospend');
|
||||||
try {
|
try {
|
||||||
mkdirSync(uploadsDir, { recursive: true });
|
mkdirSync(uploadsDir, { recursive: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { error } 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';
|
import sharp from 'sharp';
|
||||||
|
|
||||||
export const POST = (async ({ request, locals}) => {
|
export const POST = (async ({ request, locals}) => {
|
||||||
@@ -20,21 +20,21 @@ export const POST = (async ({ request, locals}) => {
|
|||||||
//}
|
//}
|
||||||
await sharp(full_res)
|
await sharp(full_res)
|
||||||
.toFormat('webp')
|
.toFormat('webp')
|
||||||
.toFile(path.join(IMAGE_DIR,
|
.toFile(path.join(env.IMAGE_DIR,
|
||||||
"rezepte",
|
"rezepte",
|
||||||
"full",
|
"full",
|
||||||
data.name + ".webp"))
|
data.name + ".webp"))
|
||||||
await sharp(full_res)
|
await sharp(full_res)
|
||||||
.resize({ width: 800})
|
.resize({ width: 800})
|
||||||
.toFormat('webp')
|
.toFormat('webp')
|
||||||
.toFile(path.join(IMAGE_DIR,
|
.toFile(path.join(env.IMAGE_DIR,
|
||||||
"rezepte",
|
"rezepte",
|
||||||
"thumb",
|
"thumb",
|
||||||
data.name + ".webp"))
|
data.name + ".webp"))
|
||||||
await sharp(full_res)
|
await sharp(full_res)
|
||||||
.resize({ width: 20})
|
.resize({ width: 20})
|
||||||
.toFormat('webp')
|
.toFormat('webp')
|
||||||
.toFile(path.join(IMAGE_DIR,
|
.toFile(path.join(env.IMAGE_DIR,
|
||||||
"rezepte",
|
"rezepte",
|
||||||
"placeholder",
|
"placeholder",
|
||||||
data.name + ".webp"))
|
data.name + ".webp"))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
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 { unlink } from 'node:fs';
|
||||||
import { error } from '@sveltejs/kit';
|
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")
|
if(!auth) throw error(401, "You need to be logged in")
|
||||||
|
|
||||||
[ "full", "thumb", "placeholder"].forEach((folder) => {
|
[ "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)
|
if(e) error(404, "could not delete: " + folder + "/" + data.name + ".webp" + e)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
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 { rename } from 'node:fs';
|
||||||
import { error } from '@sveltejs/kit';
|
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")
|
if(!auth ) throw error(401, "need to be logged in")
|
||||||
|
|
||||||
[ "full", "thumb", "placeholder"].forEach((folder) => {
|
[ "full", "thumb", "placeholder"].forEach((folder) => {
|
||||||
const old_path = path.join(IMAGE_DIR, "rezepte", folder, data.old_name + ".webp")
|
const old_path = path.join(env.IMAGE_DIR, "rezepte", folder, data.old_name + ".webp")
|
||||||
rename(old_path, path.join(IMAGE_DIR, "rezepte", folder, data.new_name + ".webp"), (e) => {
|
rename(old_path, path.join(env.IMAGE_DIR, "rezepte", folder, data.new_name + ".webp"), (e) => {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
if(e) throw error(500, "could not mv: " + old_path)
|
if(e) throw error(500, "could not mv: " + old_path)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { MONGO_URL } from '$env/static/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
let isConnected = false;
|
let isConnected = false;
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export const dbConnect = async () => {
|
|||||||
socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity
|
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;
|
isConnected = true;
|
||||||
console.log('MongoDB connected with persistent connection');
|
console.log('MongoDB connected with persistent connection');
|
||||||
|
|||||||
Reference in New Issue
Block a user