feat: ci/cd flow
This commit is contained in:
+6
-1
@@ -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=
|
||||
|
||||
@@ -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
@@ -26,4 +26,6 @@ src/lib/paraglide
|
||||
project.inlang/cache/
|
||||
# SQLite
|
||||
*.db
|
||||
CONTEXT.md
|
||||
CONTEXT.md
|
||||
build.sh
|
||||
.claude
|
||||
+6
-2
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user