mirror of
https://github.com/rajnandan1/kener.git
synced 2026-06-22 20:00:44 +00:00
chore: Update environment variables and documentation for ORIGIN requirement; refactor Docker setup for documentation indexing
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
KENER_SECRET_KEY=some_secret_key_for_kener
|
||||
REDIS_URL=redis://localhost:6379
|
||||
ORIGIN=http://localhost:3000
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Publish Main Docker Image (with Docs)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKERHUB_REGISTRY: docker.io
|
||||
GITHUB_REGISTRY: ghcr.io
|
||||
DOCKERHUB_IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}
|
||||
GITHUB_IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
concurrency:
|
||||
group: main-docker-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-push-main:
|
||||
name: Build and push main Docker images (with docs)
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [debian, alpine]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@v3.8.0
|
||||
with:
|
||||
cosign-release: v2.2.4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ${{ env.GITHUB_REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.6.1
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_IMAGE_NAME }}
|
||||
${{ env.GITHUB_REGISTRY }}/${{ env.GITHUB_IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=main-with-docs,enable=${{ matrix.variant == 'debian' }}
|
||||
type=raw,value=main-with-docs-alpine,enable=${{ matrix.variant == 'alpine' }}
|
||||
type=sha,format=short,prefix=main-with-docs-,enable=${{ matrix.variant == 'debian' }}
|
||||
type=sha,format=short,prefix=main-with-docs-alpine-,enable=${{ matrix.variant == 'alpine' }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v6.13.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
VARIANT=${{ matrix.variant }}
|
||||
WITH_DOCS=true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Sign the published Docker images
|
||||
env:
|
||||
TAGS: ${{ steps.meta.outputs.tags }}
|
||||
DIGEST: ${{ steps.build-and-push.outputs.digest }}
|
||||
run: |
|
||||
echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
|
||||
+38
-7
@@ -7,10 +7,12 @@
|
||||
# Build:
|
||||
# docker build -t kener . # Alpine (default)
|
||||
# docker build -t kener --build-arg VARIANT=debian . # Debian Slim
|
||||
# docker build -t kener --build-arg WITH_DOCS=true . # Include docs
|
||||
#
|
||||
# Run:
|
||||
# docker run -d -p 3000:3000 \
|
||||
# -e KENER_SECRET_KEY=<secret> \
|
||||
# -e ORIGIN=http://localhost:3000 \
|
||||
# -e REDIS_URL=redis://<host>:6379 \
|
||||
# -v kener_db:/app/database \
|
||||
# kener
|
||||
@@ -18,6 +20,7 @@
|
||||
|
||||
ARG NODE_VERSION=24
|
||||
ARG VARIANT=alpine
|
||||
ARG WITH_DOCS=false
|
||||
|
||||
# =============================================================================
|
||||
# STAGE 1 — BUILDER (installs deps, compiles native modules, builds app)
|
||||
@@ -62,14 +65,35 @@ COPY . .
|
||||
# 4. Create directories that the app expects
|
||||
RUN mkdir -p database
|
||||
|
||||
# 5. Remove docs routes before build (avoids EXDEV rename error in overlayfs)
|
||||
# and clean .svelte-kit so stale route types don't persist
|
||||
RUN rm -rf src/routes/\(docs\) .svelte-kit
|
||||
# 5. Conditionally remove docs routes before build
|
||||
# (avoids EXDEV rename error in overlayfs; clean .svelte-kit so stale
|
||||
# route types don't persist)
|
||||
ARG WITH_DOCS
|
||||
RUN if [ "$WITH_DOCS" != "true" ]; then \
|
||||
rm -rf src/routes/\(docs\) .svelte-kit; \
|
||||
fi
|
||||
|
||||
# 6. Build: SvelteKit (vite) + server bundle (esbuild)
|
||||
RUN npm run build
|
||||
# Use build-with-docs when docs are enabled
|
||||
RUN if [ "$WITH_DOCS" = "true" ]; then \
|
||||
npm run build-with-docs; \
|
||||
else \
|
||||
npm run build; \
|
||||
fi
|
||||
|
||||
# 7. Remove devDependencies from node_modules
|
||||
# 7. Stage docs runtime files for index-docs (empty dir when docs disabled)
|
||||
RUN mkdir -p /docs-runtime && \
|
||||
if [ "$WITH_DOCS" = "true" ]; then \
|
||||
mkdir -p /docs-runtime/scripts && \
|
||||
mkdir -p /docs-runtime/src/lib && \
|
||||
mkdir -p "/docs-runtime/src/routes/(docs)/docs" && \
|
||||
cp scripts/index-docs.ts /docs-runtime/scripts/ && \
|
||||
cp src/lib/marked.ts /docs-runtime/src/lib/ && \
|
||||
cp "src/routes/(docs)/docs.json" "/docs-runtime/src/routes/(docs)/" && \
|
||||
cp -r "src/routes/(docs)/docs/content" "/docs-runtime/src/routes/(docs)/docs/"; \
|
||||
fi
|
||||
|
||||
# 8. Remove devDependencies from node_modules
|
||||
RUN npm prune --omit=dev
|
||||
|
||||
# =============================================================================
|
||||
@@ -137,6 +161,13 @@ COPY --chown=node:node --from=builder /app/src/lib/server/templates/general
|
||||
# Build output (SvelteKit client/server + esbuild main.js) — changes most often
|
||||
COPY --chown=node:node --from=builder /app/build ./build
|
||||
|
||||
# Docs runtime files (index-docs script + markdown sources; empty when WITH_DOCS=false)
|
||||
COPY --chown=node:node --from=builder /docs-runtime/ ./
|
||||
|
||||
# Entrypoint script (runs index-docs on startup when docs are bundled)
|
||||
COPY --chown=node:node docker-entrypoint.sh ./docker-entrypoint.sh
|
||||
RUN chmod +x docker-entrypoint.sh
|
||||
|
||||
# ---- Runtime configuration ----
|
||||
|
||||
# Switch to non-root user
|
||||
@@ -148,5 +179,5 @@ EXPOSE ${PORT}
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||
CMD sh -c 'curl -sf http://localhost:${PORT}${KENER_BASE_PATH}/healthcheck || exit 1'
|
||||
|
||||
ENTRYPOINT ["node"]
|
||||
CMD ["build/main.js"]
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
CMD ["node", "build/main.js"]
|
||||
|
||||
@@ -78,14 +78,14 @@ git clone https://github.com/rajnandan1/kener.git
|
||||
cd kener
|
||||
|
||||
# Uses docker-compose.yml (includes Redis + Kener)
|
||||
# Set a strong KENER_SECRET_KEY in docker-compose.yml before first run
|
||||
# Set a strong KENER_SECRET_KEY and ORIGIN in docker-compose.yml before first run
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Open `http://localhost:3000`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Set a strong `KENER_SECRET_KEY` before starting for the first time.
|
||||
> Set a strong `KENER_SECRET_KEY` and set `ORIGIN` to your public URL before starting for the first time.
|
||||
|
||||
Use `docker-compose.dev.yml` when you want to build from local source instead of pulling the published image:
|
||||
|
||||
@@ -113,6 +113,7 @@ docker run -d \
|
||||
-p 3000:3000 \
|
||||
-v "$(pwd)/database:/app/database" \
|
||||
-e "KENER_SECRET_KEY=replace_with_a_random_string" \
|
||||
-e "ORIGIN=http://localhost:3000" \
|
||||
-e "REDIS_URL=redis://host.docker.internal:6379" \
|
||||
docker.io/rajnandan1/kener:latest
|
||||
```
|
||||
@@ -140,6 +141,7 @@ Create a `.env` with at least:
|
||||
|
||||
```dotenv
|
||||
KENER_SECRET_KEY=replace_with_a_random_string
|
||||
ORIGIN=http://localhost:3000
|
||||
REDIS_URL=redis://localhost:6379
|
||||
PORT=3000
|
||||
```
|
||||
|
||||
@@ -36,20 +36,20 @@ services:
|
||||
args:
|
||||
VARIANT: alpine # or "debian"
|
||||
NODE_VERSION: 24
|
||||
WITH_DOCS: "true"
|
||||
container_name: kener-dev
|
||||
environment:
|
||||
KENER_SECRET_KEY: dev-secret-key-for-local-testing-only
|
||||
ORIGIN: http://localhost:3000
|
||||
REDIS_URL: redis://redis:6379
|
||||
# DATABASE_URL: sqlite://./database/kener.sqlite.db
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- dev_data:/app/database
|
||||
- dev_uploads:/app/uploads
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
dev_data:
|
||||
dev_uploads:
|
||||
|
||||
+1
-3
@@ -34,6 +34,7 @@ services:
|
||||
environment:
|
||||
# ── Required ──
|
||||
KENER_SECRET_KEY: replace_me_with_a_random_string # generate: openssl rand -base64 32
|
||||
ORIGIN: http://localhost:3000 # public URL of your Kener instance (required for CSRF protection)
|
||||
REDIS_URL: redis://redis:6379
|
||||
|
||||
# ── Database (default: SQLite) ──
|
||||
@@ -59,7 +60,6 @@ services:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- data:/app/database
|
||||
- uploads:/app/uploads
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
@@ -107,8 +107,6 @@ services:
|
||||
volumes:
|
||||
data:
|
||||
name: kener_db
|
||||
uploads:
|
||||
name: kener_uploads
|
||||
redis_data:
|
||||
name: kener_redis
|
||||
# postgres_data:
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Index documentation into Redis when docs are bundled in the image
|
||||
if [ -f /app/scripts/index-docs.ts ]; then
|
||||
echo "[kener] Indexing documentation into Redis..."
|
||||
node --experimental-strip-types /app/scripts/index-docs.ts || \
|
||||
echo "[kener] Warning: docs indexing failed (is REDIS_URL set?). Continuing..."
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
@@ -16,7 +16,7 @@ import IORedis from "ioredis";
|
||||
import dotenv from "dotenv";
|
||||
import { marked } from "marked";
|
||||
import plaintify from "marked-plaintify";
|
||||
import { mdToText } from "../src/lib/marked";
|
||||
import { mdToText } from "../src/lib/marked.ts";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { handler } from "../build/handler.js";
|
||||
import { apiReference } from "@scalar/express-api-reference";
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
import express from "express";
|
||||
@@ -7,7 +6,6 @@ import Startup from "../src/lib/server/startup.ts";
|
||||
import shutdownSchedulers from "../src/lib/server/schedulers/shutdown.ts";
|
||||
import shutdownQueues from "../src/lib/server/queues/shutdown.ts";
|
||||
import dbInstance from "../src/lib/server/db/db.ts";
|
||||
import fs from "fs-extra";
|
||||
import knex from "knex";
|
||||
import knexOb from "../knexfile.js";
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ The fastest way to get started is with Docker Compose.
|
||||
git clone https://github.com/rajnandan1/kener.git
|
||||
cd kener
|
||||
|
||||
# Update KENER_SECRET_KEY in docker-compose.yml before first run
|
||||
# Update KENER_SECRET_KEY and ORIGIN in docker-compose.yml before first run
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Kener will be available at `http://localhost:3000`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Set a strong value for `KENER_SECRET_KEY` in `docker-compose.yml` before starting.
|
||||
> Set a strong value for `KENER_SECRET_KEY` and set `ORIGIN` to your public URL in `docker-compose.yml` before starting.
|
||||
|
||||
### Run pre-built image {#run-pre-built-image-docker-hub-or-ghcr}
|
||||
|
||||
@@ -57,6 +57,7 @@ Minimum `.env` for Docker:
|
||||
|
||||
```dotenv
|
||||
KENER_SECRET_KEY=replace_with_a_random_string
|
||||
ORIGIN=http://localhost:3000
|
||||
REDIS_URL=redis://host.docker.internal:6379
|
||||
PORT=3000
|
||||
```
|
||||
@@ -70,6 +71,7 @@ docker run -d \
|
||||
-p 3000:3000 \
|
||||
-v "$(pwd)/database:/app/database" \
|
||||
-e "KENER_SECRET_KEY=replace_with_a_random_string" \
|
||||
-e "ORIGIN=http://localhost:3000" \
|
||||
-e "REDIS_URL=redis://host.docker.internal:6379" \
|
||||
docker.io/rajnandan1/kener:latest
|
||||
```
|
||||
@@ -122,6 +124,7 @@ Create or update your `.env`:
|
||||
|
||||
```dotenv
|
||||
KENER_SECRET_KEY=replace_with_a_random_string
|
||||
ORIGIN=http://localhost:3000
|
||||
REDIS_URL=redis://localhost:6379
|
||||
PORT=3000
|
||||
# Optional (defaults to SQLite):
|
||||
|
||||
@@ -44,6 +44,38 @@ node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
|
||||
> [!WARNING]
|
||||
> Without `KENER_SECRET_KEY`, Kener will use a default key which is **not secure for production**. You'll see warnings in the console if running without this variable.
|
||||
|
||||
### ORIGIN {#origin}
|
||||
|
||||
**Purpose**: The public-facing URL of your Kener instance. Required by SvelteKit for CSRF protection in production.
|
||||
|
||||
**Why It's Required**: In production builds, SvelteKit validates that POST form submissions originate from the same site. Without `ORIGIN`, all form submissions (login, signup, settings, etc.) will fail with a **"Cross-site POST form submissions are forbidden"** error.
|
||||
|
||||
**Requirements**:
|
||||
|
||||
- Must include protocol (`http://` or `https://`)
|
||||
- Must match the URL users access in their browser
|
||||
- No trailing slash
|
||||
- Not needed during local development (`vite dev` infers it automatically)
|
||||
|
||||
**Examples**:
|
||||
|
||||
```bash
|
||||
# Local Docker testing
|
||||
ORIGIN=http://localhost:3000
|
||||
|
||||
# Production
|
||||
ORIGIN=https://status.example.com
|
||||
|
||||
# With custom port
|
||||
ORIGIN=https://status.example.com:8443
|
||||
|
||||
# With base path
|
||||
ORIGIN=https://example.com
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> Without `ORIGIN`, **all form submissions will be rejected** in production. This includes login, signup, and admin panel actions. Always set this variable when deploying.
|
||||
|
||||
## Optional Variables {#optional-variables}
|
||||
|
||||
### KENER_BASE_PATH {#kener-base-path}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<script lang="ts">
|
||||
import DotsIcon from "@lucide/svelte/icons/camera";
|
||||
import FolderIcon from "@lucide/svelte/icons/folder";
|
||||
import Share3Icon from "@lucide/svelte/icons/share";
|
||||
import TrashIcon from "@lucide/svelte/icons/trash";
|
||||
import type { Component } from "svelte";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
let { items }: { items: { name: string; url: string; icon: Component }[] } = $props();
|
||||
const sidebar = Sidebar.useSidebar();
|
||||
</script>
|
||||
|
||||
<Sidebar.Group class="group-data-[collapsible=icon]:hidden">
|
||||
<Sidebar.GroupLabel>Documents</Sidebar.GroupLabel>
|
||||
<Sidebar.Menu>
|
||||
{#each items as item (item.name)}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton>
|
||||
{#snippet child({ props })}
|
||||
<a {...props} href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Sidebar.MenuAction {...props} showOnHover class="data-[state=open]:bg-accent rounded-sm">
|
||||
<DotsIcon />
|
||||
<span class="sr-only">More</span>
|
||||
</Sidebar.MenuAction>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-24 rounded-lg"
|
||||
side={sidebar.isMobile ? "bottom" : "right"}
|
||||
align={sidebar.isMobile ? "end" : "start"}
|
||||
>
|
||||
<DropdownMenu.Item>
|
||||
<FolderIcon />
|
||||
<span>Open</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>
|
||||
<Share3Icon />
|
||||
<span>Share</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item variant="destructive">
|
||||
<TrashIcon />
|
||||
<span>Delete</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Sidebar.MenuItem>
|
||||
{/each}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton class="text-sidebar-foreground/70">
|
||||
<DotsIcon class="text-sidebar-foreground/70" />
|
||||
<span>More</span>
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.Group>
|
||||
@@ -1,7 +1,4 @@
|
||||
<script lang="ts">
|
||||
import CirclePlusFilledIcon from "@lucide/svelte/icons/circle-plus";
|
||||
import MailIcon from "@lucide/svelte/icons/mail";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import { page } from "$app/state";
|
||||
import type { Component } from "svelte";
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||
import type { WithoutChildren } from "$lib/utils.js";
|
||||
import type { Component, ComponentProps } from "svelte";
|
||||
let {
|
||||
items,
|
||||
...restProps
|
||||
}: { items: { title: string; url: string; icon: Component }[] } & WithoutChildren<
|
||||
ComponentProps<typeof Sidebar.Group>
|
||||
> = $props();
|
||||
</script>
|
||||
|
||||
<Sidebar.Group {...restProps}>
|
||||
<Sidebar.GroupContent>
|
||||
<Sidebar.Menu>
|
||||
{#each items as item (item.title)}
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton>
|
||||
{#snippet child({ props })}
|
||||
<a href={item.url} {...props}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
{/each}
|
||||
</Sidebar.Menu>
|
||||
</Sidebar.GroupContent>
|
||||
</Sidebar.Group>
|
||||
@@ -20,12 +20,14 @@
|
||||
href={clientResolver(resolve, "/")}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
class="dark:text-foreground hidden sm:flex"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Status Page
|
||||
</Button>
|
||||
<Button href="https://kener.ing/docs" variant="secondary" size="sm" target="_blank" rel="noopener noreferrer">
|
||||
Documentation
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user