Skip to content

misospace/miso-gallery

Repository files navigation

Miso Gallery

GHCR Build Release License

A lightweight, self-hosted image gallery for AI-generated images.

Features

  • 🍲 Simple: Flask-based, minimal dependencies
  • 📱 Mobile-friendly: PWA-ready responsive design
  • 🔐 Authentication: OIDC (Authentik, Okta, Google) + local password
  • 🖼️ Thumbnails: Auto-generated thumbnail caching
  • 🗑️ Bulk operations: Multi-select delete
  • 🔄 Refresh: Live refresh button
  • 🐳 Containerized: Docker + Kubernetes deployment ready

Quick Start

Docker

docker run -d --name miso-gallery \
  -p 5000:5000 \
  -v /path/to/images:/data \
  ghcr.io/misospace/miso-gallery:latest

Docker Compose

services:
  miso-gallery:
    image: ghcr.io/misospace/miso-gallery:latest
    ports:
      - "5000:5000"
    volumes:
      - ./images:/data
    environment:
      - ADMIN_PASSWORD=your-password

Configuration

Environment Variables

Variable Required Default Description
DATA_FOLDER No /data Path to image directory
IMAGE_BASE_URL No - Base URL for shareable links
PORT No 5000 Server port

Authentication

Miso Gallery supports two authentication methods:

Local Password (Default)

docker run -d \
  -e ADMIN_PASSWORD=your-password \
  ghcr.io/misospace/miso-gallery:latest

OIDC / Authentik

docker run -d \
  -e AUTH_TYPE=oidc \
  -e OIDC_ISSUER=https://authentik.yourdomain.com \
  -e OIDC_CLIENT_ID=miso-gallery \
  -e OIDC_CLIENT_SECRET=your-secret \
  -e OIDC_CALLBACK_URL=https://miso-gallery.yourdomain.com/auth/callback \
  -e SECRET_KEY=your-session-secret \
  ghcr.io/misospace/miso-gallery:latest

Auth Configuration Options

Variable Required Default Description
AUTH_TYPE No local Auth method: local, oidc, or none
ADMIN_PASSWORD If local - Password for local auth (plaintext or Werkzeug hash: pbkdf2: / scrypt:)
SECRET_KEY Yes - Flask secret for sessions. Generate with: python -c "import secrets; print(secrets.token_urlsafe(48))"
LLM_READ_API_KEYS No - Comma-separated Bearer tokens with read scope (list, view, thumbnails). Write-scoped keys are also accepted here.
LLM_WRITE_API_KEYS No - Comma-separated Bearer tokens with write scope (delete, dedup, bulk operations, task execution).
LLM_API_KEYS No Legacy single var; functions as both read and write. Deprecated in favour of LLM_READ_API_KEYS / LLM_WRITE_API_KEYS.
OIDC_ISSUER If OIDC - OIDC provider URL (e.g., https://authentik.example.com)
OIDC_CLIENT_ID If OIDC - OIDC client ID
OIDC_CLIENT_SECRET If OIDC - OIDC client secret
OIDC_CALLBACK_URL If OIDC - Callback URL for OIDC

Rate Limiting

Variable Required Default Description
RATE_LIMIT_REDIS_URL No - Redis/Dragonfly URL for shared rate-limit state (falls back to in-memory if unset/unreachable)
RATE_LIMIT_PREFIX No miso-gallery:ratelimit Key prefix for rate-limit entries
RATE_LIMIT_ROUTE_LIMITS No - JSON overrides per endpoint, e.g. {"auth":{"max_requests":5,"window":300}}

See docs/rate-limit-shared-backend.md for recommended production rollout and migration plan.

Authentik Setup

  1. Create an Application in Authentik
  2. Create a Provider (OpenID Connect) with these settings:
    • Client ID: miso-gallery
    • Client Secret: Generate a secure secret
    • Signing Key: Select default
    • Redirect URIs: https://miso-gallery.yourdomain.com/auth/callback
  3. Copy the Provider URL (issuer) to OIDC_ISSUER

Kubernetes Deployment

HelmRelease Example

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: miso-gallery
  namespace: apps
spec:
  chart:
    spec:
      chart: app-template
      version: 3.0
  values:
    controllers:
      miso-gallery:
        containers:
          app:
            image:
              repository: ghcr.io/misospace/miso-gallery
              tag: latest
            env:
              - name: ADMIN_PASSWORD
                valueFrom:
                  secretKeyRef:
                    name: miso-gallery-secrets
                    key: password
            persistence:
              data:
                type: nfs
                server: nfs.yourdomain.com
                path: /path/to/images
    service:
      app:
        ports:
          http:
            port: 5000
    route:
      app:
        hostnames:
          - gallery.yourdomain.com
        parentRefs:
          - name: envoy-external

Features

Thumbnails

Thumbnails are automatically generated and cached in .thumb_cache/ directory. This improves loading performance for large galleries.

  • Max size: 400x400
  • Format: Optimized JPEG
  • Auto-refresh: Thumbnails regenerate when source image changes

Multi-Select

  • Click checkboxes on images to select
  • Use "Select All" / "Deselect All" buttons
  • Bulk delete selected images

Direct Image Access

Even when authentication is enabled, direct URLs to images remain publicly accessible:

  • /view/folder/image.jpg - Full resolution
  • /thumb/folder/image.jpg - Thumbnail

This allows sharing images while protecting the gallery UI.

LLM API

Miso Gallery includes a JSON API intended for LLM agents and other machine-to-machine clients. The primary purpose is to let an external LLM client inspect and manage gallery state: list/search media, read metadata, tag, delete, bulk-delete, and deduplicate images.

Enable the API by configuring one or more API keys. The desired model:

  • Read keys (LLM_READ_API_KEYS) grant access to list, view, and thumbnail endpoints.
  • Write keys (LLM_WRITE_API_KEYS) grant access to delete, dedup, bulk operations, and task execution. A write key also works on read endpoints (write implies read).
  • Legacy keys (LLM_API_KEYS) function as both read and write — supported for backward compatibility but deprecated.

Prefer separate read and write keys for new deployments; LLM_API_KEYS remains supported as a legacy all-purpose key. When explicit LLM_READ_API_KEYS or LLM_WRITE_API_KEYS are set, the legacy LLM_API_KEYS value is ignored.

docker run -d --name miso-gallery \
  -p 5000:5000 \
  -v /path/to/images:/data \
  -e SECRET_KEY=your-session-secret \
  -e ADMIN_PASSWORD=your-password \
  -e LLM_READ_API_KEYS=gallery-read-key \
  -e LLM_WRITE_API_KEYS=gallery-write-key \
  ghcr.io/misospace/miso-gallery:latest

Authenticate each request with a Bearer token:

curl -H "Authorization: Bearer gallery-read-key" \
  http://localhost:5000/api/llm/images

LLM API endpoints are token-authenticated and do not require CSRF tokens. Existing browser/session authentication continues to work for the UI.

Read Endpoints

Method Path Description
GET /api/llm/images?q=<query> Recursively list/search media. q matches filenames or relative paths.
GET /api/llm/image/<relpath> Return metadata for a single image/video.
GET /api/llm/recent?limit=N Return recent media sorted by modification time. Default 50, max 500.
GET /api/llm/folders Return folder listing with relative paths and parent folders.

Example:

curl -H "Authorization: Bearer gallery-read-key" \
  "http://localhost:5000/api/llm/images?q=cat"

Response shape:

{
  "count": 1,
  "images": [
    {
      "name": "cat.jpg",
      "rel_path": "cats/cat.jpg",
      "media_type": "image",
      "size": 12345,
      "size_human": "12.1 KB",
      "modified": "2026-04-28T12:34:56Z",
      "mtime": 1777398896.0,
      "view_url": "/view/cats/cat.jpg",
      "thumb_url": "/thumb/cats/cat.jpg"
    }
  ]
}

Write Endpoints

Method Path Body Description
POST /api/llm/tags {"rel_path":"cats/cat.jpg","tag":"favorite","action":"add"} Add/remove tags. Tag persistence is currently log-only.
POST /api/llm/delete {"rel_path":"cats/cat.jpg"} Move one media file to trash and clear its thumbnail cache.
POST /api/llm/bulk-delete {"rel_paths":["a.jpg","b.jpg"]} Move multiple media files to trash.
POST /api/llm/dedup {} or {"remove":true} Find duplicate media by SHA-256. Defaults to dry-run; remove:true moves duplicates to trash.

Delete example:

curl -X POST \
  -H "Authorization: Bearer gallery-write-key" \
  -H "Content-Type: application/json" \
  -d '{"rel_path":"cats/cat.jpg"}' \
  http://localhost:5000/api/llm/delete

Dedup dry-run example:

curl -X POST \
  -H "Authorization: Bearer gallery-write-key" \
  -H "Content-Type: application/json" \
  -d '{}' \
  http://localhost:5000/api/llm/dedup

Optional: Server-Side Task Execution

Most LLM integrations do not need task execution. Use the gallery-management endpoints above unless you intentionally want Miso Gallery to expose a small set of preconfigured server-side automation commands.

Task execution is an optional advanced feature that reuses the existing webhook task infrastructure. It is disabled unless WEBHOOK_ENABLED=true, and only commands explicitly configured through WEBHOOK_TASK_* environment variables can be run. This can be useful for trusted maintenance or generation scripts that should run on the gallery host, but it is not required for normal LLM-to-gallery interaction.

docker run -d --name miso-gallery \
  -p 5000:5000 \
  -v /path/to/images:/data \
  -e SECRET_KEY=your-session-secret \
  -e LLM_READ_API_KEYS=gallery-read-key \
  -e LLM_WRITE_API_KEYS=gallery-write-key \
  -e WEBHOOK_ENABLED=true \
  -e 'WEBHOOK_TASK_GENERATE=python3 /data/scripts/generate.py {params.prompt}' \
  ghcr.io/misospace/miso-gallery:latest

Run a configured task:

curl -X POST \
  -H "Authorization: Bearer gallery-write-key" \
  -H "Content-Type: application/json" \
  -d '{"task":"generate","params":{"prompt":"a cozy bowl of miso soup"}}' \
  http://localhost:5000/api/llm/task/run

Task execution requires a write-scoped LLM API key. Read-scoped keys can browse gallery metadata but cannot run configured server-side tasks. Task commands run from DATA_FOLDER, scalar params are shell-quoted, and the timeout is controlled by WEBHOOK_TASK_TIMEOUT with a default of 30 seconds.

Development

Local Development

# Clone and setup
git clone https://github.com/misospace/miso-gallery.git
cd miso-gallery

# Create virtual environment
python3 -m venv venv
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Run development server
python app.py

Building

# Build Docker image
docker build -t miso-gallery:latest .

# Run locally
docker run -p 5000:5000 -v ./images:/data miso-gallery:latest

Releases

Use the Manual Release GitHub Actions workflow and enter a version like 0.4.6. It normalizes v0.4.6 to 0.4.6, updates the in-app version string in app.py, pushes that bump to main through the configured bot identity, creates the plain-semver tag, and creates the GitHub release with generated notes.

The Build workflow (triggered by a published release) validates that APP_VERSION in app.py matches the release tag before building Docker images. If they diverge, the build fails to prevent releasing a binary with an incorrect version string.

License

MIT

About

Lightweight image gallery + manager

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors