Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e1ca337
add dockerignore
catalysync Apr 21, 2026
0533b04
add dockerfile
catalysync Apr 21, 2026
65dadd6
add docker compose
catalysync Apr 21, 2026
aa31a4c
add env example
catalysync Apr 21, 2026
afacf9e
add justfile
catalysync Apr 21, 2026
aa5f540
add postgres deps
catalysync Apr 21, 2026
a9d6f67
init alembic
catalysync Apr 21, 2026
f8c6a91
add precommit config
catalysync Apr 21, 2026
520ce45
add ty check to ci
catalysync Apr 21, 2026
cb9df44
add docker ci workflow
catalysync Apr 21, 2026
ecada59
add db session and mixins
catalysync Apr 21, 2026
8a9fc21
add org model
catalysync Apr 21, 2026
9ef8043
add user and group models
catalysync Apr 21, 2026
1dd9628
add workspace and environment
catalysync Apr 21, 2026
5e5e791
add membership model
catalysync Apr 21, 2026
26c9864
add api token and audit models
catalysync Apr 21, 2026
eba17a7
add initial migration
catalysync Apr 21, 2026
87e389e
add org and workspace endpoints
catalysync Apr 21, 2026
f2f9478
drop billing_status, vendor-neutral auth
catalysync Apr 21, 2026
477935c
add event bus and feature resolver
catalysync Apr 21, 2026
a6d6573
add live ready probes
catalysync Apr 21, 2026
dce5400
add license and lifespan hooks
catalysync Apr 21, 2026
d496e84
add frontend scaffold
catalysync Apr 21, 2026
439694a
Merge branch 'feat/v0-infra' into feat/initial-data-model
catalysync Apr 21, 2026
b2297f0
add outbox for durable events
catalysync Apr 21, 2026
3db268f
test settings subclass pattern
catalysync Apr 21, 2026
cf70d83
add secret backend protocol
catalysync Apr 21, 2026
4854add
add auth layer
catalysync Apr 21, 2026
3dc6105
wire toml and profiles into settings
catalysync Apr 22, 2026
93937f5
silence unused arg warning
catalysync Apr 22, 2026
b5be05e
neutralize downstream refs in docs
catalysync Apr 22, 2026
be49603
neutralize downstream refs in docs
catalysync Apr 22, 2026
c6cccc3
docs: add development, deployment, release-please
catalysync Apr 22, 2026
2377fb9
feat: openapi dump script and integration tests
catalysync Apr 22, 2026
219c0f8
test: drop sqlite, run against real postgres
catalysync Apr 22, 2026
b320941
feat: constrained config fields and doc generation
catalysync Apr 22, 2026
6b2e76c
fix: dispose main engine on app shutdown
catalysync Apr 22, 2026
f558a11
feat: openapi-driven cli foundations
catalysync Apr 22, 2026
d01ebc6
ops: graceful-shutdown grace period and prestop
catalysync Apr 22, 2026
afa125d
remove tool name from commit rule
catalysync Apr 23, 2026
68983e1
use dedicated test database
catalysync Apr 25, 2026
4bf2348
auto-commit dep + app_name tag
catalysync Apr 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# vcs
.git
.gitignore
.github

# python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
*.egg-info
.eggs
build
dist

# venv + caches
.venv
.uv-cache
.pytest_cache
.coverage
.coverage.*
htmlcov
.mypy_cache
.ruff_cache

# editors
.vscode
.idea
*.swp
.DS_Store

# docker
Dockerfile*
.dockerignore
docker-compose*.yml

# secrets
.env
.env.local
.env.*.local

# tests + tooling not needed in image
tests
notes
scratch
*.md
58 changes: 58 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# nagara environment template.
# Copy to .env and fill in values for local dev. NEVER commit a real .env.
#
# Every variable has a sensible default in src/nagara/config.py — only set
# the ones you need to override.

# ── App ─────────────────────────────────────────────────────────────────
# Selects which .env file is loaded and switches behavior helpers.
# One of: development | test | staging | production
NAGARA_ENV=development

# Logging level for the app (matches Python logging values).
NAGARA_LOG_LEVEL=INFO

# ── Security (REQUIRED in production) ──────────────────────────────────
# Used to sign sessions, JWTs, etc. In production, generate with:
# python -c "import secrets; print(secrets.token_urlsafe(64))"
# Empty string here means "unset" — verify_settings() will reject it in prod.
NAGARA_SECRET_KEY=

# ── HTTP ────────────────────────────────────────────────────────────────
# Public-facing base URL. Used to build absolute links in emails, webhooks, etc.
NAGARA_BASE_URL=http://127.0.0.1:8000

# Frontend URL — used by the API to redirect after auth flows.
NAGARA_FRONTEND_BASE_URL=http://127.0.0.1:3000

# Comma-separated list of allowed CORS origins. Empty = none.
# Pydantic settings parses JSON arrays for list fields:
# NAGARA_CORS_ORIGINS=["http://localhost:3000"]
NAGARA_CORS_ORIGINS=[]

# ── Database ────────────────────────────────────────────────────────────
# Override the assembled DSN entirely (Render/Heroku/Supabase typically
# inject DATABASE_URL — both NAGARA_DATABASE_URL and DATABASE_URL work).
# DATABASE_URL=postgresql+asyncpg://user:pwd@host:5432/dbname

# Or set the parts and we'll assemble the DSN at runtime.
NAGARA_POSTGRES_USER=nagara
NAGARA_POSTGRES_PWD=nagara
NAGARA_POSTGRES_HOST=127.0.0.1
NAGARA_POSTGRES_PORT=5432
NAGARA_POSTGRES_DB=nagara

# Pool tuning. Defaults are reasonable for a single-process dev app.
# NAGARA_DATABASE_POOL_SIZE=5
# NAGARA_DATABASE_POOL_RECYCLE_SECONDS=600
# NAGARA_DATABASE_COMMAND_TIMEOUT_SECONDS=30

# ── Sessions ───────────────────────────────────────────────────────────
# How long a user session lasts before requiring re-auth.
# Pydantic parses this as a timedelta (e.g., "P31D" / "31 days").
# NAGARA_USER_SESSION_TTL=31 days

# ── Profile (optional, CLI feature) ─────────────────────────────────────
# Selects an active profile from ~/.config/nagara/profiles.toml
# (only applies if you've set up profiles via the CLI).
# NAGARA_PROFILE=dev
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ on:
jobs:
check:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: nagara
POSTGRES_PASSWORD: nagara
POSTGRES_DB: nagara
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

env:
NAGARA_POSTGRES_HOST: 127.0.0.1
NAGARA_POSTGRES_PORT: "5432"
NAGARA_POSTGRES_USER: nagara
NAGARA_POSTGRES_PWD: nagara
NAGARA_POSTGRES_DB: nagara

steps:
- uses: actions/checkout@v4

Expand All @@ -26,5 +49,8 @@ jobs:
- name: ruff lint
run: uv run ruff check

- name: ty check
run: uv run ty check

- name: pytest
run: uv run pytest
58 changes: 58 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: docker

on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- name: set up qemu
uses: docker/setup-qemu-action@v3

- name: set up buildx
uses: docker/setup-buildx-action@v3

- name: log in to ghcr
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha

- name: build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
18 changes: 18 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: release-please

on:
push:
branches: [main]

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: release

on:
push:
tags: ["v*"]
workflow_dispatch:

jobs:
build-and-publish:
runs-on: ubuntu-latest
# PyPI trusted publishing needs id-token: write; contents: read for checkout.
permissions:
id-token: write
contents: read
environment:
name: pypi
url: https://pypi.org/p/nagara
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: build wheel and sdist
run: uv build

- name: publish to pypi
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ htmlcov/
# logs
*.log

# node / frontend
node_modules/
.next/
out/
coverage/
*.tsbuildinfo
next-env.d.ts

# local agent/tooling state — never commit
.claude/
.aider*
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
29 changes: 29 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Run with `uv run pre-commit run --all-files` or install once with
# `uv run pre-commit install` to wire it into `git commit`.
#
# Hooks intentionally mirror what `just check` runs in CI so failures
# surface locally before they hit a workflow run.

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-merge-conflict
- id: check-added-large-files
args: ["--maxkb=512"]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.11
hooks:
- id: ruff-check
args: ["--fix"]
- id: ruff-format

- repo: https://github.com/astral-sh/ty-pre-commit
rev: 0.0.32
hooks:
- id: ty
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.next
node_modules
dist
build
coverage
pnpm-lock.yaml
11 changes: 11 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
}
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.1.0"
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Changelog

Release notes are generated by [release-please](https://github.com/googleapis/release-please)
from Conventional Commit messages on merge to `main`.
63 changes: 63 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# syntax=docker/dockerfile:1.7

# Multi-stage Dockerfile.
# Stage 1: build a frozen virtualenv with uv using the locked deps.
# Stage 2: copy that venv into a minimal runtime image, run as a non-root user.

# ── Builder ─────────────────────────────────────────────────────────────────
FROM python:3.14-slim-bookworm AS builder

# uv is published as a tiny static binary — copy it from the official image
# rather than installing via pip (avoids wheel build + cache pollution).
COPY --from=ghcr.io/astral-sh/uv:0.11.7 /uv /uvx /usr/local/bin/

ENV UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1 \
UV_PYTHON_DOWNLOADS=never \
PYTHONDONTWRITEBYTECODE=1

WORKDIR /app

# Install dependencies first (cache layer survives source changes).
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-install-project --no-dev

# Now install the project itself.
COPY src ./src
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev

# ── Runtime ─────────────────────────────────────────────────────────────────
FROM python:3.14-slim-bookworm AS runtime

# tini reaps zombies + handles signals correctly under uvicorn.
RUN apt-get update \
&& apt-get install --no-install-recommends -y tini curl \
&& rm -rf /var/lib/apt/lists/*

# Non-root user. Pinned uid/gid so volume mounts behave on hosts that care.
RUN groupadd --system --gid 1001 nagara \
&& useradd --system --uid 1001 --gid nagara --create-home --shell /sbin/nologin nagara

WORKDIR /app

# Copy the venv and source from the builder.
COPY --from=builder --chown=nagara:nagara /app/.venv /app/.venv
COPY --from=builder --chown=nagara:nagara /app/src /app/src
COPY --chown=nagara:nagara pyproject.toml /app/pyproject.toml

ENV PATH="/app/.venv/bin:${PATH}" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PORT=8000

USER nagara

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl --fail --silent http://127.0.0.1:${PORT}/health || exit 1

ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["uvicorn", "nagara.main:app", "--host", "0.0.0.0", "--port", "8000"]
Loading