--- url: 'https://ashim-hq.github.io/ashim/api/ai.md' --- # AI Engine Reference The `@ashim/ai` package bridges Node.js to a **persistent Python sidecar** for all ML operations. The dispatcher process stays alive between requests for fast warm-start performance. GPU is auto-detected at startup and used when available. 13 AI tool routes. All models run locally - no internet required after initial model download. ## Architecture ``` Node.js Tool Route │ ▼ @ashim/ai bridge.ts │ (stdin/stdout JSON + stderr progress events) ▼ Python dispatcher (persistent process) │ ├─ remove_bg.py (rembg / BiRefNet) ├─ upscale.py (RealESRGAN) ├─ inpaint.py (LaMa ONNX) ├─ ocr.py (PaddleOCR / Tesseract) ├─ detect_faces.py (MediaPipe) ├─ face_landmarks.py (MediaPipe landmarks) ├─ enhance_faces.py (GFPGAN / CodeFormer) ├─ colorize.py (DDColor) ├─ noise_removal.py (tiered denoising) ├─ red_eye_removal.py (landmark + color analysis) ├─ restore.py (scratch repair + enhancement + denoising) └─ seam_carving (Go caire binary - not Python) ``` **Timeouts:** 300 s default; OCR and BiRefNet background removal get 600 s. ## Background Removal **Function:** `removeBackground`\ **Tool route:** `remove-background`\ **Model:** rembg with BiRefNet (default) or U2-Net variants | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `model` | string | `birefnet-general` | Model variant - see table below | | `alphaMattingForeground` | number (1–255) | 240 | Foreground threshold for alpha matting | | `alphaMattingBackground` | number (1–255) | 10 | Background threshold for alpha matting | | `returnMask` | boolean | false | Return the mask instead of the cutout | | `backgroundColor` | string | - | Fill removed area (hex color or "transparent") | **Available models:** | Model ID | Best for | |----------|---------| | `birefnet-general` | General purpose (default) | | `birefnet-portrait` | People / portraits | | `birefnet-dis` | Dichotomous Image Segmentation | | `birefnet-hrsod` | High-resolution salient objects | | `birefnet-cod` | Camouflaged objects | | `u2net` | Fast general purpose | | `u2net_human_seg` | Human segmentation | | `isnet-general-use` | High quality general | ## Image Upscaling **Function:** `upscale`\ **Tool route:** `upscale`\ **Model:** RealESRGAN (with Lanczos fallback on CPU-constrained systems) | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `scale` | 2 | 4 | 4 | Upscale factor | | `model` | string | `realesrgan-x4plus` | Model variant | | `faceEnhance` | boolean | false | Apply GFPGAN face enhancement pass | | `denoise` | number (0–1) | 0.5 | Denoising strength | | `format` | string | - | Output format override | | `quality` | number | 95 | Output quality (for JPEG/WebP) | ## OCR / Text Extraction **Function:** `extractText`\ **Tool route:** `ocr`\ **Models:** Tesseract (fast), PaddleOCR PP-OCRv5 (balanced), PaddleOCR-VL 1.5 (best) | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `quality` | `fast` | `balanced` | `best` | `balanced` | Processing tier | | `language` | string | `en` | Language code (ISO 639-1) | | `enhance` | boolean | false | Pre-process image to improve OCR accuracy | Returns structured results with bounding boxes, confidence scores, and extracted text blocks. ## Face / PII Blur **Function:** `blurFaces`\ **Tool route:** `blur-faces`\ **Model:** MediaPipe face detection | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `blurRadius` | number | 30 | Gaussian blur radius | | `sensitivity` | number (0–1) | 0.5 | Detection confidence threshold | ## Face Enhancement **Function:** `enhanceFaces`\ **Tool route:** `enhance-faces`\ **Models:** GFPGAN, CodeFormer | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `model` | `gfpgan` | `codeformer` | `gfpgan` | Enhancement model | | `strength` | number (0–1) | 0.7 | Enhancement strength | | `sensitivity` | number (0–1) | 0.5 | Face detection threshold | | `centerFace` | boolean | false | Focus enhancement on center face only | ## AI Colorization **Function:** `colorize`\ **Tool route:** `colorize`\ **Model:** DDColor (with OpenCV DNN fallback) Converts black-and-white or grayscale photos to full color. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `intensity` | number (0–1) | 0.85 | Color saturation strength | | `model` | string | `ddcolor` | Model variant | ## Noise Removal **Function:** `noiseRemoval`\ **Tool route:** `noise-removal` Three-tier denoising pipeline (fast: OpenCV bilateral filter; balanced: frequency-domain; best: deep learning model). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `quality` | `fast` | `balanced` | `best` | `balanced` | Processing tier | | `strength` | number (0–1) | 0.5 | Denoising strength | | `preserveDetail` | boolean | true | Edge-preserving mode | | `colorNoise` | boolean | false | Target color noise specifically | ## Red Eye Removal **Function:** `removeRedEye`\ **Tool route:** `red-eye-removal` Detects face landmarks, locates eye regions, and corrects red-channel oversaturation. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `sensitivity` | number (0–1) | 0.5 | Red pixel detection threshold | | `strength` | number (0–1) | 0.9 | Correction strength | ## Photo Restoration **Function:** `restorePhoto`\ **Tool route:** `restore-photo` Multi-step pipeline for old or damaged photos: scratch/tear detection and repair → face enhancement → denoising → optional colorization. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `mode` | `auto` | `light` | `heavy` | `auto` | Restoration intensity | | `scratchRemoval` | boolean | true | Detect and repair scratches, tears | | `faceEnhancement` | boolean | true | Apply face enhancement pass | | `fidelity` | number (0–1) | 0.7 | Face enhancement strength | | `denoise` | boolean | true | Apply denoising pass | | `denoiseStrength` | number (0–100) | 40 | Denoising strength | | `colorize` | boolean | false | Colorize after restoration | ## Passport Photo **Function:** Uses `detectFaceLandmarks` + `removeBackground`\ **Tool route:** `passport-photo`\ **Model:** MediaPipe face landmarks Generates government-compliant ID photos. Supports **37 countries** across 6 regions (Americas, Europe, Asia, Africa, Oceania, Middle East). Each spec includes physical dimensions, DPI, head-height ratio, eye-line position, and background color requirements. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `country` | string | `us` | ISO country code (see list in UI) | | `printLayout` | `4x6` | `A4` | `none` | `none` | Output as print sheet or standalone | | `backgroundColor` | string | country default | Background fill color | ## Object Erasing (Inpainting) **Function:** `inpaint`\ **Tool route:** `erase-object`\ **Model:** LaMa via ONNX Runtime | Parameter | Type | Required | Description | |-----------|------|---------|-------------| | `maskData` | string | Yes | Base64-encoded PNG mask (white = erase) | | `maskThreshold` | number (0–255) | No | Threshold for mask binarization | GPU-accelerated when an NVIDIA GPU is available. ## Smart Crop **Function:** Uses MediaPipe + Sharp attention/entropy\ **Tool route:** `smart-crop`\ **Model:** MediaPipe face detection | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `mode` | `subject` | `face` | `trim` | `subject` | Crop strategy | | `width` | number | - | Output width | | `height` | number | - | Output height | | `facePreset` | string | - | Preset framing when `mode=face` | **Face presets:** | Preset | Head ratio | Best for | |--------|-----------|---------| | `close-up` | 1.8× face | Headshots | | `head-and-shoulders` | 2.8× face | Profile photos | | `upper-body` | 4.5× face | LinkedIn / formal | | `half-body` | 7.0× face | Full upper body | ## Content-Aware Resize (Seam Carving) **Function:** `seamCarve`\ **Tool route:** `content-aware-resize`\ **Engine:** Go `caire` binary (not Python - no GPU benefit) Intelligently resizes images by removing or adding low-energy seams, preserving important content. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `width` | number | - | Target width | | `height` | number | - | Target height | | `protectFaces` | boolean | true | Protect detected face regions from seam removal | | `blurRadius` | number | 0 | Pre-blur to reduce noise sensitivity | | `sobelThreshold` | number | 10 | Edge sensitivity threshold | | `square` | boolean | false | Force square output | Max input edge before auto-downscaling: **1200 px**. --- --- url: 'https://ashim-hq.github.io/ashim/guide/architecture.md' --- # Architecture ashim is a monorepo managed with pnpm workspaces and Turborepo. Everything ships as a single Docker container. ## Project structure ``` ashim/ ├── apps/ │ ├── api/ # Fastify backend │ ├── web/ # React + Vite frontend │ └── docs/ # This VitePress site ├── packages/ │ ├── image-engine/ # Sharp-based image operations │ ├── ai/ # Python AI model bridge │ └── shared/ # Types, constants, i18n └── docker/ # Dockerfile and Compose config ``` ## Packages ### `@ashim/image-engine` The core image processing library built on [Sharp](https://sharp.pixelplumbing.com/). It handles all non-AI operations: resize, crop, rotate, flip, convert, compress, strip metadata, and color adjustments (brightness, contrast, saturation, grayscale, sepia, invert, color channels). This package has no network dependencies and runs entirely in-process. ### `@ashim/ai` A bridge layer that calls Python scripts for ML operations. On first use, the bridge starts a persistent Python dispatcher process that pre-imports heavy libraries (PIL, NumPy, MediaPipe, rembg) so subsequent AI calls skip the import overhead. If the dispatcher is not yet ready, the bridge falls back to spawning a fresh Python subprocess per request. **Models are not pre-loaded.** Each tool script loads its model weights from disk at request time and discards them when the request finishes. See [Resource footprint](#resource-footprint) for the full memory profile. Supported operations: background removal (rembg/BiRefNet), upscaling (RealESRGAN), face blur (MediaPipe), face enhancement (GFPGAN/CodeFormer), object erasing (LaMa ONNX), OCR (PaddleOCR/Tesseract), colorization (DDColor), noise removal, red eye removal, photo restoration, passport photo generation, and content-aware resize (Go caire binary). Python scripts live in `packages/ai/python/`. The Docker image pre-downloads all model weights during the build so the container works fully offline. ### `@ashim/shared` Shared TypeScript types, constants (like `APP_VERSION` and tool definitions), and i18n translation strings used by both the frontend and backend. ## Applications ### API (`apps/api`) A Fastify v5 server exposing 47 tool routes (34 standard image operations + 13 AI-powered) that handles: * File uploads, temporary workspace management, and persistent file storage * User file library with version chains (`user_files` table) -- each processed result links back to its source file and records which tool was applied, with auto-generated thumbnails for the Files page * Tool execution (routes each tool request to the image engine or AI bridge) * Pipeline orchestration (chaining multiple tools sequentially) * Batch processing with concurrency control via p-queue * User authentication, RBAC (admin/user roles with a full permission set), API key management, and rate limiting * Teams management -- admin-only CRUD; users are assigned to a team via the `team` field on their profile * Runtime settings -- a key-value store in the `settings` table that controls `disabledTools`, `enableExperimentalTools`, `loginAttemptLimit`, and other operational knobs without redeploying * Custom branding -- logo upload endpoint; the uploaded image is stored at `data/branding/logo.png` and served to the frontend * Swagger/OpenAPI documentation at `/api/docs` * Serving the built frontend as a SPA in production Key dependencies: Fastify, Drizzle ORM, better-sqlite3, Sharp, Piscina (worker thread pool), Zod for validation. The server handles graceful shutdown on SIGTERM/SIGINT: it drains HTTP connections, stops the worker pool, shuts down the Python dispatcher, and closes the database. ### Web (`apps/web`) A React 19 single-page app built with Vite. Uses Zustand for state management, Tailwind CSS v4 for styling, and Lucide for icons. Communicates with the API over REST and SSE (for progress tracking). Pages include a tool workspace, a Files page for managing persistent uploads and results, an automation/pipeline builder, and an admin settings panel. The built frontend gets served by the Fastify backend in production, so there is no separate web server in the Docker container. ### Docs (`apps/docs`) This VitePress site. Deployed to GitHub Pages automatically on push to `main`. ## How a request flows 1. The user picks a tool in the web UI and uploads an image. 2. The frontend sends a multipart POST to `/api/v1/tools/:toolId` with the file and settings. 3. The API route validates the input with Zod, then dispatches processing. 4. For standard tools, the request is offloaded to a Piscina worker thread pool so Sharp operations don't block the main event loop. The worker auto-orients the image based on EXIF metadata, runs the tool's process function, and returns the result. If the worker pool is unavailable, processing falls back to the main thread. 5. For AI tools, the TypeScript bridge sends a request to the persistent Python dispatcher (or spawns a fresh subprocess as fallback), waits for it to finish, and reads the output file. 6. Job progress is persisted to the `jobs` SQLite table so state survives container restarts. Real-time updates are delivered via SSE at `/api/v1/jobs/:jobId/progress`. 7. The API returns a `jobId` and `downloadUrl`. The user downloads the processed image from `/api/v1/download/:jobId/:filename`. For pipelines, the API feeds the output of each step as input to the next, running them sequentially. For batch processing, the API uses p-queue with a configurable concurrency limit (`CONCURRENT_JOBS`) and returns a ZIP file with all processed images. ## Resource footprint ashim is designed for low idle memory use. Nothing is preloaded or kept warm at startup. ### At idle Only the Node.js/Fastify process is running. Typical idle RAM is **~100-150 MB** (Node.js process + SQLite connection). No Python process, no worker threads, no model weights in memory. ### What starts, and when | Component | Starts when | Memory while active | |-----------|-------------|---------------------| | Fastify server | Container start | ~100-150 MB | | Piscina worker threads | First standard tool request | Spawned on demand, terminated after **30 s idle** | | Python dispatcher | First AI tool request | Python interpreter + pre-imported libraries (PIL, NumPy, MediaPipe, rembg) - no model weights | | AI model weights | During the specific tool's request | Loaded from disk, freed when the request finishes | ### Model loading All model weight files (totalling several GB) sit on disk in `/opt/models/` at all times. Each AI tool script loads only its own model(s) into memory for the duration of a request, then releases them. Some scripts explicitly call `del model` and `torch.cuda.empty_cache()` after inference to ensure memory is returned immediately. There is no model cache between requests. Running the same AI tool back-to-back reloads the model each time. This keeps idle memory near zero at the cost of a model-load delay on every AI request. ### First AI request cold start The Python dispatcher is not running when the container starts. The first AI request triggers two things in parallel: the dispatcher starts warming up in the background, and the request itself falls back to a one-off Python subprocess spawn. Once the dispatcher signals ready, all subsequent AI requests use it directly and skip the subprocess spawn cost. --- --- url: 'https://ashim-hq.github.io/ashim/guide/configuration.md' --- # Configuration All configuration is done through environment variables. Every variable has a sensible default, so ashim works out of the box without setting any of them. ## Environment variables ### Server | Variable | Default | Description | |---|---|---| | `PORT` | `1349` | Port the server listens on. | | `RATE_LIMIT_PER_MIN` | `100` | Maximum requests per minute per IP. | ### Authentication | Variable | Default | Description | |---|---|---| | `AUTH_ENABLED` | `false` | Set to `true` to require login. The Docker image defaults to `true`. | | `DEFAULT_USERNAME` | `admin` | Username for the initial admin account. Only used on first run. | | `DEFAULT_PASSWORD` | `admin` | Password for the initial admin account. Change this after first login. | | `MAX_USERS` | `5` | Maximum number of registered user accounts | | `SKIP_MUST_CHANGE_PASSWORD` | - | Set to any non-empty value to bypass the forced password-change prompt on first login | ### Storage | Variable | Default | Description | |---|---|---| | `STORAGE_MODE` | `local` | `local` or `s3`. Only local storage is currently implemented. | | `DB_PATH` | `./data/ashim.db` | Path to the SQLite database file. | | `WORKSPACE_PATH` | `./tmp/workspace` | Directory for temporary files during processing. Cleaned up automatically. | | `FILES_STORAGE_PATH` | `./data/files` | Directory for persistent user files (uploaded images, saved results). | ### Processing limits | Variable | Default | Description | |---|---|---| | `MAX_UPLOAD_SIZE_MB` | `100` | Maximum file size per upload in megabytes. | | `MAX_BATCH_SIZE` | `200` | Maximum number of files in a single batch request. | | `CONCURRENT_JOBS` | `3` | Number of batch jobs that run in parallel. Higher values use more memory. | | `MAX_MEGAPIXELS` | `100` | Maximum image resolution allowed. Rejects images larger than this. | ### Cleanup | Variable | Default | Description | |---|---|---| | `FILE_MAX_AGE_HOURS` | `24` | How long temporary files are kept before automatic deletion. | | `CLEANUP_INTERVAL_MINUTES` | `30` | How often the cleanup job runs. | ### Appearance | Variable | Default | Description | |---|---|---| | `APP_NAME` | `ashim` | Display name shown in the UI. | | `DEFAULT_THEME` | `light` | Default theme for new sessions. `light` or `dark`. | | `DEFAULT_LOCALE` | `en` | Default interface language. | ## Docker example ```yaml services: ashim: image: ashimhq/ashim:latest ports: - "1349:1349" volumes: - ashim-data:/data - ashim-workspace:/tmp/workspace environment: - AUTH_ENABLED=true - DEFAULT_USERNAME=admin - DEFAULT_PASSWORD=changeme - MAX_UPLOAD_SIZE_MB=200 - CONCURRENT_JOBS=4 - FILE_MAX_AGE_HOURS=12 restart: unless-stopped ``` ## Volumes The Docker container uses two volumes: * `/data` -- Persistent storage for the SQLite database and user files. Mount this to keep users, API keys, saved pipelines, and uploaded images across container restarts. * `/tmp/workspace` -- Temporary storage for images being processed. This can be ephemeral, but mounting it avoids filling up the container's writable layer. --- --- url: 'https://ashim-hq.github.io/ashim/guide/contributing.md' --- # Contributing Thanks for your interest in ashim. Community feedback helps shape the project, and there are several ways to get involved. ## How to contribute The best way to contribute is through [GitHub Issues](https://github.com/ashim-hq/ashim/issues): * **Bug reports** - Found something broken? Open a bug report with steps to reproduce, your Docker setup, and what you expected to happen. * **Feature requests** - Have an idea for a new tool or improvement? Describe the problem you want solved and why it matters to you. * **Feedback** - Thoughts on the UI, workflow, documentation, or anything else? We want to hear it. ## Pull requests We do not accept pull requests. All development is handled internally to maintain architectural consistency and code quality across the project. If you have found a bug, open an issue describing it rather than submitting a fix. If you have a suggestion for how something should work, describe it in a feature request. Your input is valuable even without a code contribution. ## Forking You are welcome to fork the project for your own use under the terms of the [AGPLv3 license](https://github.com/ashim-hq/ashim/blob/main/LICENSE). The [Developer Guide](/guide/developer) covers setup, architecture, and how to add new tools. ## Security If you discover a security vulnerability, please report it privately through [GitHub Security Advisories](https://github.com/ashim-hq/ashim/security/advisories/new) rather than opening a public issue. --- --- url: 'https://ashim-hq.github.io/ashim/guide/database.md' --- # Database ashim uses SQLite with [Drizzle ORM](https://orm.drizzle.team/) for data persistence. The schema is defined in `apps/api/src/db/schema.ts`. The database file lives at the path set by `DB_PATH` (defaults to `./data/ashim.db`). In Docker, mount the `/data` volume to persist it across container restarts. ## Tables ### users Stores user accounts. Created automatically on first run from `DEFAULT_USERNAME` and `DEFAULT_PASSWORD`. | Column | Type | Notes | |---|---|---| | `id` | integer | Primary key, auto-increment | | `username` | text | Unique, required | | `passwordHash` | text | bcrypt hash | | `role` | text | `admin` or `user` | | `mustChangePassword` | integer | Boolean flag for forced password reset | | `createdAt` | text | ISO timestamp | | `updatedAt` | text | ISO timestamp | ### sessions Active login sessions. Each row ties a session token to a user. | Column | Type | Notes | |---|---|---| | `id` | text | Primary key (session token) | | `userId` | integer | Foreign key to `users.id` | | `expiresAt` | text | ISO timestamp | | `createdAt` | text | ISO timestamp | ### teams Groups for organizing users. Admins can assign users to teams. | Column | Type | Description | |--------|------|-------------| | `id` | text UUID | Primary key | | `name` | text (unique, max 50 chars) | Team name | | `createdAt` | integer | Unix timestamp | ### api\_keys API keys for programmatic access. The raw key is shown once on creation; only the hash is stored. | Column | Type | Notes | |---|---|---| | `id` | integer | Primary key, auto-increment | | `userId` | integer | Foreign key to `users.id` | | `keyHash` | text | SHA-256 hash of the key | | `name` | text | User-provided label | | `createdAt` | text | ISO timestamp | | `lastUsedAt` | text | Updated on each authenticated request | Keys are prefixed with `si_` followed by 96 hex characters (48 random bytes). ### pipelines Saved tool chains that users create in the UI. | Column | Type | Notes | |---|---|---| | `id` | integer | Primary key, auto-increment | | `name` | text | Pipeline name | | `description` | text | Optional description | | `steps` | text | JSON array of `{ toolId, settings }` objects | | `createdAt` | text | ISO timestamp | ### user\_files Persistent file library with version chain tracking. Each processing step that saves a result creates a new row linked to its parent via `parentId`, forming a version tree. | Column | Type | Description | |--------|------|-------------| | `id` | text UUID | Primary key | | `userId` | text UUID | FK → users (CASCADE DELETE) | | `originalName` | text | Original upload filename | | `storedName` | text | Filename on disk | | `mimeType` | text | MIME type | | `size` | integer | File size in bytes | | `width` | integer | Image width in px | | `height` | integer | Image height in px | | `version` | integer | Version number (1 = original) | | `parentId` | text UUID | null | FK → user\_files (parent version) | | `toolChain` | text (JSON array) | Tool IDs applied in order to produce this version | | `createdAt` | integer | Unix timestamp | ### jobs Tracks processing jobs for progress reporting and cleanup. | Column | Type | Notes | |---|---|---| | `id` | text | Primary key (UUID) | | `type` | text | Tool or pipeline identifier | | `status` | text | `queued`, `processing`, `completed`, or `failed` | | `progress` | real | 0.0–1.0 fraction | | `inputFiles` | text | JSON array of input file paths | | `outputPath` | text | Path to the result file | | `settings` | text | JSON of the tool settings used | | `error` | text | Error message if failed | | `createdAt` | text | ISO timestamp | | `completedAt` | text | ISO timestamp | ### settings Key-value store for server-wide settings that admins can change from the UI. | Column | Type | Notes | |---|---|---| | `key` | text | Primary key | | `value` | text | Setting value | | `updatedAt` | text | ISO timestamp | ## Migrations Drizzle handles schema migrations. The config is in `apps/api/drizzle.config.ts`. During development, run: ```bash pnpm --filter @ashim/api drizzle-kit push ``` In production, the schema is applied automatically on startup. --- --- url: 'https://ashim-hq.github.io/ashim/guide/deployment.md' --- # Deployment ashim ships as a single Docker container. The image supports **linux/amd64** (with NVIDIA CUDA) and **linux/arm64** (CPU), so it runs natively on Intel/AMD servers, Apple Silicon Macs, and ARM devices like the Raspberry Pi 4/5. See [Docker Image](./docker-tags) for GPU setup, Docker Compose examples, and version pinning. ## Docker Compose (recommended) ```yaml services: ashim: image: ashimhq/ashim:latest container_name: ashim ports: - "1349:1349" volumes: - ashim-data:/data - ashim-workspace:/tmp/workspace environment: - AUTH_ENABLED=true - DEFAULT_USERNAME=admin - DEFAULT_PASSWORD=admin restart: unless-stopped volumes: ashim-data: ashim-workspace: ``` ```bash docker compose up -d ``` The app is then available at `http://localhost:1349`. > **Docker Hub rate limits?** Replace `ashimhq/ashim:latest` with `ghcr.io/ashim-hq/ashim:latest` to pull from GitHub Container Registry instead. Both registries receive the same image on every release. ## What's inside the container The Docker image uses a multi-stage build: 1. **Build stage** -- Installs Node.js dependencies and builds the React frontend with Vite. 2. **Production stage** -- Copies the built frontend and API source into a Node 22 image, installs system dependencies (Python 3, ImageMagick, Tesseract, potrace), sets up a Python virtual environment with all ML packages, and pre-downloads model weights. Everything runs from a single process. The Fastify server handles API requests and serves the frontend SPA. ### System dependencies installed in the image * Python 3 with pip * ImageMagick * Tesseract OCR * libraw (RAW image support) * potrace (bitmap to vector conversion) ### Python packages * rembg with BiRefNet-Lite (background removal) * RealESRGAN (upscaling) * PaddleOCR (text recognition) * MediaPipe (face detection) * OpenCV (inpainting/object removal) * onnxruntime, opencv-python, Pillow, numpy Model weights are downloaded at build time, so the container works fully offline. ### Architecture notes All tools work on both amd64 and arm64. AI tools (background removal, upscaling, OCR, face detection) use CUDA-accelerated packages on amd64 and CPU packages on arm64. GPU acceleration is auto-detected at runtime when `--gpus all` is passed. ## Volumes Mount these to persist data: | Mount point | Purpose | |---|---| | `/data` | SQLite database (users, API keys, pipelines, settings) | | `/tmp/workspace` | Temporary image processing files | The `/data` volume is the important one. Without it, you lose all user accounts and saved pipelines on container restart. The workspace volume is optional but prevents the container's writable layer from growing. ## Health check The container includes a health check that hits `GET /api/v1/health`. Docker uses this to report container status: ```bash docker inspect --format='{{.State.Health.Status}}' ashim ``` ## Reverse proxy If you're running ashim behind nginx or Caddy, point it at port 1349. Example nginx config: ```nginx server { listen 80; server_name images.example.com; client_max_body_size 200M; location / { proxy_pass http://localhost:1349; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` Set `client_max_body_size` to match your `MAX_UPLOAD_SIZE_MB` value. ## CI/CD The GitHub repository has three workflows: * **ci.yml** -- Runs automatically on every push and PR. Lints, typechecks, tests, builds, and validates the Docker image (without pushing). * **release.yml** -- Triggered manually via `workflow_dispatch`. Runs semantic-release to create a version tag and GitHub release, then builds a multi-arch Docker image (amd64 + arm64) and pushes to Docker Hub (`ashimhq/ashim`) and GitHub Container Registry (`ghcr.io/ashim-hq/ashim`). * **deploy-docs.yml** -- Builds this documentation site and deploys it to GitHub Pages on push to `main`. To create a release, go to **Actions > Release > Run workflow** in the GitHub UI, or run: ```bash gh workflow run release.yml ``` Semantic-release determines the version from commit history. The `latest` Docker tag always points to the most recent release. --- --- url: 'https://ashim-hq.github.io/ashim/guide/developer.md' --- # Developer guide How to set up a local development environment and contribute code to ashim. ## Prerequisites * [Node.js](https://nodejs.org/) 22+ * [pnpm](https://pnpm.io/) 9+ (`corepack enable && corepack prepare pnpm@latest --activate`) * [Docker](https://www.docker.com/) (for container builds and AI features) * Git Python 3.10+ is only needed if you are working on the AI/ML sidecar (background removal, upscaling, OCR). ## Setup ```bash git clone https://github.com/ashim-hq/ashim.git cd ashim pnpm install pnpm dev ``` This starts two dev servers: | Service | URL | Notes | |----------|--------------------------|------------------------------------| | Frontend | http://localhost:1349 | Vite dev server, proxies /api | | Backend | http://localhost:13490 | Fastify API (accessed via proxy) | Open http://localhost:1349 in your browser. Login with `admin` / `admin`. You will be prompted to change the password on first login. ## Project structure ``` apps/ api/ Fastify backend web/ Vite + React frontend docs/ VitePress documentation (this site) packages/ shared/ Constants, types, i18n strings image-engine/ Sharp-based image operations ai/ Python sidecar bridge for ML models tests/ unit/ Vitest unit tests integration/ Vitest integration tests (full API) e2e/ Playwright end-to-end specs fixtures/ Small test images ``` ## Commands ```bash pnpm dev # start frontend + backend pnpm build # build all workspaces pnpm typecheck # TypeScript check across monorepo pnpm lint # Biome lint + format check pnpm lint:fix # auto-fix lint + format pnpm test # unit + integration tests pnpm test:unit # unit tests only pnpm test:integration # integration tests only pnpm test:e2e # Playwright e2e tests pnpm test:coverage # tests with coverage report ``` ## Code conventions * Double quotes, semicolons, 2-space indentation (enforced by Biome) * ES modules in all workspaces * [Conventional commits](https://www.conventionalcommits.org/) for semantic-release * Zod for all API input validation * No modifications to Biome, TypeScript, or editor config files. Fix the code, not the linter. ## Database SQLite via Drizzle ORM. The database file lives at `./data/ashim.db` by default. ```bash cd apps/api npx drizzle-kit generate # generate a migration from schema changes npx drizzle-kit migrate # apply pending migrations ``` Schema is defined in `apps/api/src/db/schema.ts`. Tables: users, sessions, settings, jobs, apiKeys, pipelines, teams, userFiles. ## Adding a new tool Every tool follows the same pattern. Here is a minimal example. ### 1. Backend route Create `apps/api/src/routes/tools/my-tool.ts`: ```ts import { z } from "zod"; import type { FastifyInstance } from "fastify"; import { createToolRoute } from "../tool-factory.js"; const settingsSchema = z.object({ intensity: z.number().min(0).max(100).default(50), }); export function registerMyTool(app: FastifyInstance) { createToolRoute(app, { toolId: "my-tool", settingsSchema, async process(inputBuffer, settings, filename) { // Use sharp or other libraries to process the image const sharp = (await import("sharp")).default; const result = await sharp(inputBuffer) // ... your processing logic .toBuffer(); return { buffer: result, filename: filename.replace(/\.[^.]+$/, ".png"), contentType: "image/png", }; }, }); } ``` Then register it in `apps/api/src/routes/tools/index.ts`. ### 2. Frontend settings component Create `apps/web/src/components/tools/my-tool-settings.tsx`: ```tsx import { useState } from "react"; import { useToolProcessor } from "@/hooks/use-tool-processor"; import { useFileStore } from "@/stores/file-store"; export function MyToolSettings() { const { files } = useFileStore(); const { processFiles, processing, error, downloadUrl } = useToolProcessor("my-tool"); const [intensity, setIntensity] = useState(50); const handleProcess = () => { processFiles(files, { intensity }); }; return (