# Step-by-Step Migration Guide
## Prerequisites
- GitHub CLI (`gh`) installed and authenticated
- Copier installed: `uv tool install copier` or `pipx install copier`
- Access to the template repository
---
## Step 1: Create New Repo from Template
```bash
# Create new repository from template
copier copy --trust gh:kamiwaza-internal/kamiwaza-extensions-template kamiwaza-extension-{name}
cd kamiwaza-extension-{name}
# Initialize git
git init
git add -A
git commit -m "Initial commit from template"
```
You'll be prompted for:
- project_name: Repository name (e.g., "acme-extensions")
- github_org: GitHub organization (default: "kamiwaza-internal")
- docker_org: Docker Hub org prefix (default: "kamiwazaai")
---
## Step 2: Create GitHub Repository
```bash
# Create the GitHub repo
gh repo create kamiwaza-internal/kamiwaza-extension-{name} --private --source=. --push
# Or if the repo already exists on GitHub:
git remote add origin git@github.com:your-org/my-extensions.git
git push -u origin main
```
---
## Step 4: Create Develop Branch
```bash
# If the setup script didn't create it:
git checkout -b develop
git push -u origin develop
```
## Step 5: Create Feature Branch for Migration
```bash
git checkout develop
git checkout -b feature/migrate-existing-extension
```
## Step 6: Copy Existing Extension Code
```bash
# Copy your existing code into the new structure
# For an app with frontend/backend:
cp -r /path/to/app/* apps/my-existing-app
# Remove .git
rm apps/my-existing-app/.git
```
## Step 6a: Rename Files and Create Metadata
The build system expects specific file names. Rename your docker-compose files if needed:
```bash
# Rename docker-compose files to expected names
mv apps/my-existing-app/docker-compose.local.yml apps/my-existing-app/docker-compose.yml
mv apps/my-existing-app/docker-compose.app-garden.yml apps/my-existing-app/docker-compose.appgarden.yml
```
**Required file names:**
- `docker-compose.yml` - Local development compose file
- `docker-compose.appgarden.yml` - App Garden compose file (auto-generated by `make sync-compose`)
- `kamiwaza.json` - Extension metadata
**Create `kamiwaza.json`** in your app directory:
```json
{
"name": "my-existing-app",
"version": "1.0.0",
"source_type": "kamiwaza",
"visibility": "public",
"description": "Description of your application",
"risk_tier": 1,
"verified": false,
"preferred_model_type": "reasoning",
"env_defaults": {
"KAMIWAZA_API_URI": "http://host.docker.internal:7777/api"
}
}
```
**Add image tags to `docker-compose.yml`** for each service with a `build` directive:
```yaml
services:
frontend:
build: ./frontend
image: kamiwazaai/my-existing-app-frontend:v1.0.0 # Add this line
# ... rest of config
backend:
build: ./backend
image: kamiwazaai/my-existing-app-backend:v1.0.0 # Add this line
# ... rest of config
```
> [!NOTE]
> Image tags should match the version in `kamiwaza.json` with a `v` prefix (e.g., version `1.0.0` → tag `v1.0.0`)
## Step 7: Retrofit to Use Shared Auth/Client Libraries
For Python Backend:
```bash
# Build the shared library wheel
make package-libs PYTHON_ONLY=1
# Copy to your backend
cp shared/python/dist/kamiwaza_auth-*.whl apps/my-existing-app/backend/
```
**Update `requirements.txt`** - Add the wheel at the top:
```txt
# Kamiwaza auth shared library (bundled wheel)
./kamiwaza_auth-0.1.0-py3-none-any.whl
# ... rest of your dependencies
```
**Update `Dockerfile`** - Copy the wheel file before `pip install`:
```dockerfile
WORKDIR /app
# Copy requirements and wheel file, then install Python dependencies
COPY requirements.txt .
COPY kamiwaza_auth-*.whl .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of the application
COPY . .
```
> [!IMPORTANT]
> The wheel file must be copied before running `pip install` since `requirements.txt` references it as a local file (`./kamiwaza_auth-*.whl`).
Update your FastAPI app to use shared auth:
```python
# main.py
from fastapi import FastAPI
from kamiwaza_auth import create_session_router
app = FastAPI()
# Add session endpoints (/session, /auth/login-url, /auth/logout)
app.include_router(create_session_router())
```
### Passing User Auth to AI/Model Calls
When your app makes calls to Kamiwaza models (via OpenAI-compatible endpoints), you need to pass the user's authentication token. The user's `access_token` cookie is automatically sent with browser requests - you just need to forward it to your AI client.
**For synchronous API endpoints** (direct model calls):
```python
# api/routes/ai_features.py
from fastapi import APIRouter, Request, Depends
import os
from ...services.my_ai_client import MyAIClient
router = APIRouter()
def get_ai_client(request: Request):
"""Dependency that extracts user auth and creates AI client"""
# Extract access token from user's session cookie
access_token = request.cookies.get("access_token")
if access_token:
# Set in environment for AI client to use
os.environ['KAMIWAZA_ACCESS_TOKEN'] = access_token
return MyAIClient()
@router.post("/api/analyze")
async def analyze(request: Request, ai_client: MyAIClient = Depends(get_ai_client)):
"""Endpoint that calls AI model with user's auth"""
result = await ai_client.generate(...)
return result
```
**For background workers** (async job processing):
When using job queues (RQ, Celery, etc.), pass the token through the queue:
```python
# api/routes/documents.py
from fastapi import APIRouter, Request
@router.post("/process/{doc_id}")
async def process_document(doc_id: str, request: Request):
# Extract token from request
access_token = request.cookies.get("access_token")
# Pass to worker via job queue
job_id = await queue.enqueue(
"app.tasks.process_document",
doc_id,
access_token=access_token # Forward the token
)
return {"job_id": job_id}
```
```python
# tasks.py
import os
async def process_document(doc_id: str, access_token: str = None):
"""Background task that uses user's auth"""
if access_token:
os.environ['KAMIWAZA_ACCESS_TOKEN'] = access_token
# Now AI client will use the token
ai_client = MyAIClient()
result = await ai_client.process(doc_id)
return result
```
**In your AI client**, read the token from environment:
```python
# services/my_ai_client.py
import os
def get_auth_token() -> str:
"""Get auth token - prefers per-request token over static API key"""
return os.getenv('KAMIWAZA_ACCESS_TOKEN') or os.getenv('KAMIWAZA_API_KEY') or ""
class MyAIClient:
def connect(self):
# ... create OpenAI client ...
# Patch with auth token
auth_token = get_auth_token()
if auth_token:
self.openai_client.api_key = auth_token
```
> [!NOTE]
> The user's `access_token` flows automatically through browser requests. You just need to:
> 1. Extract it from `request.cookies.get("access_token")`
> 2. Set it in the environment or pass it to your AI client
> 3. For background workers, pass it through the job queue
### Token Refresh for Long-Running Tasks
Access tokens expire after ~5 minutes. For tasks that take longer (e.g., processing large documents), pass the `refresh_token` and refresh before expiry:
```python
# api/routes/documents.py - Pass both tokens to worker
@router.post("/process/{doc_id}")
async def process_document(doc_id: str, request: Request):
access_token = request.cookies.get("access_token")
refresh_token = request.cookies.get("access_token_refresh") # Kamiwaza cookie name
job_id = await queue.enqueue(
"app.tasks.process_document",
doc_id,
access_token=access_token,
refresh_token=refresh_token
)
return {"job_id": job_id}
```
```python
# services/token_refresh.py
import os
import time
import jwt
import httpx
async def refresh_token_if_needed() -> str:
"""Check token expiry and refresh if needed. Call before each API request."""
token = os.getenv('KAMIWAZA_ACCESS_TOKEN', '')
if not token:
return ""
# Check if expiring within 60 seconds
try:
claims = jwt.decode(token, options={"verify_signature": False})
if claims.get("exp", 0) - time.time() > 60:
return token # Still valid
except:
return token
# Refresh the token
refresh_token = os.getenv('KAMIWAZA_REFRESH_TOKEN')
if not refresh_token:
return token
api_base = os.getenv('KAMIWAZA_API_URI', 'http://host.docker.internal:7777/api')
async with httpx.AsyncClient(timeout=10.0, verify=False) as client:
resp = await client.post(
f"{api_base.rstrip('/')}/auth/refresh",
cookies={"refresh_token": refresh_token}
)
if resp.status_code == 200:
new_token = resp.json().get("access_token")
if new_token:
os.environ['KAMIWAZA_ACCESS_TOKEN'] = new_token
return new_token
return token
```
```python
# In your AI client - refresh before each API call
class MyAIClient:
async def generate(self, prompt: str):
# Refresh token if needed before making the call
new_token = await refresh_token_if_needed()
if new_token:
self.openai_client.api_key = new_token
return await self.openai_client.chat.completions.create(...)
```
> [!TIP]
> Add `PyJWT` to your `requirements.txt` for JWT decoding.
## Step 7a: Retrofit Frontend (Optional)
Only necessary if frontend needs to call Kamiwaza APIs directly (e.g., listing models, deployments).
For TypeScript Frontend :
```bash
# Build the shared TypeScript libraries (builds both kamiwaza-auth and kamiwaza-client)
make package-libs TS_ONLY=1
# Copy to your frontend
cp shared/typescript/kamiwaza-client/kamiwaza-client-*.tgz apps/my-existing-app/frontend/
cp shared/typescript/kamiwaza-auth/kamiwaza-auth-*.tgz apps/my-existing-app/frontend/ # Optional: for session management
```
Update apps/my-existing-app/frontend/package.json:
```typescript
{
"dependencies": {
"@kamiwaza/client": "file:./kamiwaza-client-0.1.0.tgz"
}
}
```
Use in your frontend:
```typescript
import {
KamiwazaClient,
ForwardAuthAuthenticator,
forwardAuthHeaders
} from '@kamiwaza/client';
// For App Garden apps (forward auth headers from request)
export async function getKamiwazaClient(request: Request) {
const authHeaders = forwardAuthHeaders(request.headers);
return new KamiwazaClient({
baseUrl: process.env.KAMIWAZA_API_URL || 'http://host.docker.internal:7777/api',
authenticator: new ForwardAuthAuthenticator(authHeaders),
});
}
// Usage in API route
export async function GET(request: Request) {
const client = await getKamiwazaClient(request);
const deployments = await client.serving.listActiveDeployments();
return Response.json(deployments);
}
```
## Step 8: Build, Sync and Validate
```bash
make build-all
make test
# Generate App Garden compose file
make sync-compose
# Validate everything
make validate
# create the registry
make build-registry
# push to your local instance of kamiwaza
make kamiwaza-push TYPE=app NAME={your app name}
```