feat: ci/cd flow

This commit is contained in:
2026-06-26 10:40:34 +02:00
parent 08d0c18fe3
commit 43d350bd32
7 changed files with 123 additions and 6 deletions
+6 -1
View File
@@ -1,2 +1,7 @@
# Drizzle
# Drizzle (local dev only; the container sets its own DATABASE_URL)
DATABASE_URL=file:local.db
# docker compose — generate secrets with: openssl rand -base64 32
ORIGIN=http://localhost:3000
CRYPTO_SECRET=
BETTER_AUTH_SECRET=
+41
View File
@@ -0,0 +1,41 @@
# Auto-create a Gitea release when a vX.Y.Z tag is pushed.
# Image build/push is handled by Komodo, so this only cuts the release.
name: release
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # need full history to diff against the previous tag
- name: Create release from tag
run: |
TAG="${{ github.ref_name }}"
PREV=$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || true)
RANGE="${PREV:+$PREV..}$TAG"
LOG=$(git log --pretty='format:%s|%h' "$RANGE")
group() { echo "$LOG" | awk -F'|' -v re="$1" '$1 ~ re { sub(re, "", $1); printf "- %s (%s)\n", $1, $2 }'; }
BREAKING=$(group '^[a-z]+[^:]*!: ')
FEATS=$(group '^feat[^:]*: ')
FIXES=$(group '^fix[^:]*: ')
OTHER=$(echo "$LOG" | awk -F'|' '$1 !~ /^(feat|fix)[^:]*!?: / { printf "- %s (%s)\n", $1, $2 }')
BODY=""
[ -n "$BREAKING" ] && BODY="$BODY### Breaking"$'\n'"$BREAKING"$'\n\n'
[ -n "$FEATS" ] && BODY="$BODY### Features"$'\n'"$FEATS"$'\n\n'
[ -n "$FIXES" ] && BODY="$BODY### Fixes"$'\n'"$FIXES"$'\n\n'
[ -n "$OTHER" ] && BODY="$BODY### Other"$'\n'"$OTHER"$'\n\n'
[ -n "$PREV" ] && BODY="$BODY**Full changelog:** ${{ github.server_url }}/${{ github.repository }}/compare/$PREV...$TAG"
jq -n --arg tag "$TAG" --arg body "$BODY" \
'{tag_name:$tag, name:$tag, body:$body}' \
| curl -fsSL -X POST "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/json" \
--data @-
+3 -1
View File
@@ -26,4 +26,6 @@ src/lib/paraglide
project.inlang/cache/
# SQLite
*.db
CONTEXT.md
CONTEXT.md
build.sh
.claude
+6 -2
View File
@@ -15,10 +15,14 @@ FROM base AS release
ENV NODE_ENV=production \
DATABASE_URL=/app/data/db.sqlite \
HOST=0.0.0.0 \
PORT=3000
PORT=3000 \
ORIGIN=http://localhost:3000 \
REPOSITORY_URL=https://tea.urania.dev/api/v1/repos/urania/nadir-agent/releases/latest
# full node_modules: drizzle-kit (devDep) is needed for `drizzle-kit migrate` at startup
WORKDIR /app
COPY --from=install /app/node_modules node_modules
COPY --from=build /app/build build
COPY --from=build /app/config config
COPY drizzle drizzle
COPY drizzle.config.ts package.json ./
RUN mkdir -p /app/data
@@ -27,4 +31,4 @@ RUN bun run db:migrate
VOLUME /app/data
EXPOSE 3000
# apply migrations (creates db.sqlite if absent), then start the server
CMD ["bun ./build/index.js"]
CMD ["sh", "-c", "bun run db:migrate && bun /app/build/index.js"]
+20
View File
@@ -0,0 +1,20 @@
# Example stack. nadir-agent runs on each managed host, not here — this is just the web UI.
# Secrets come from a .env file next to this one (see .env.example), never committed.
services:
nadir-frontend:
# pull from your private registry, or `docker compose build` locally
image: uraniadev/nadir:latest
container_name: nadir-webui
build: .
restart: unless-stopped
ports:
- "3000:3000"
environment:
ORIGIN: ${ORIGIN:-http://localhost:3000}
CRYPTO_SECRET: ${CRYPTO_SECRET:?set CRYPTO_SECRET in .env (openssl rand -base64 32)}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET in .env (openssl rand -base64 32)}
volumes:
- nadir-db:/app/data # db.sqlite folder
volumes:
nadir-db:
Executable
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Cut a release: bump package.json, tag, push, build+push image, create Gitea release.
# Usage: ./release.sh [patch|minor|major] (default: patch)
set -euo pipefail
cd "$(dirname "$0")"
BUMP="${1:-patch}"
GITEA_API="${GITEA_API:-https://tea.urania.dev/api/v1}"
REPO="${REPO:-urania/nadir-webui}"
: "${GITEA_TOKEN:?set GITEA_TOKEN (Gitea > Settings > Applications > token with repo scope)}"
[ -z "$(git status --porcelain)" ] || { echo "working tree dirty, commit first"; exit 1; }
# bumps version in package.json AND creates commit + tag vX.Y.Z
bun pm version "$BUMP"
VERSION=$(bun -e 'console.log(require("./package.json").version)')
TAG="v$VERSION"
git push --follow-tags
# build + push the multi-arch image at this version
./build.sh
# create the Gitea release from the tag
curl -fsSL -X POST "$GITEA_API/repos/$REPO/releases" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\"}" >/dev/null
echo "released $TAG"
+17 -2
View File
@@ -1,12 +1,27 @@
import { type Handle, redirect } from '@sveltejs/kit';
import { type Handle, redirect, type ServerInit } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { building, dev } from '$app/environment';
import { getAuth } from '$lib/auth/server';
import { getConfig } from '$lib/server/config';
import { getTextDirection } from '$lib/paraglide/runtime';
import { paraglideMiddleware } from '$lib/paraglide/server';
import { getConfig } from '$lib/server/config';
import { svelteKitHandler } from 'better-auth/svelte-kit';
export const init: ServerInit = () => {
const env = process.env;
const errors: string[] = [];
for (const key of ['CRYPTO_SECRET', 'BETTER_AUTH_SECRET', 'REPOSITORY_URL'])
if (!env[key]?.trim()) errors.push(`${key} is missing`);
for (const key of ['CRYPTO_SECRET', 'BETTER_AUTH_SECRET'])
if (env[key] && env[key]!.length < 32)
errors.push(`${key} is too short (need >= 32 chars; openssl rand -base64 32)`);
if (errors.length)
throw new Error(`Invalid environment:\n - ${errors.join('\n - ')}`);
};
const handleBetterAuth: Handle = async ({ event, resolve }) => {
const cfg = getConfig();
const auth = getAuth();