A high-performance web-based photo gallery server written in Rust using the Axum web framework. Tenrankai provides a responsive gallery interface with automatic image resizing, metadata extraction, and intelligent caching.
It's a gallery, CMS, and blog platform relying on nothing more than folders and files. Simply drop files in, or even use SyncThing to keep your gallery or website up to date.
The name "Tenrankai" (展覧会) is Japanese for "exhibition" or "gallery show", reflecting the project's purpose as a platform for displaying photographic collections.
- Responsive Web Gallery: Mobile-friendly masonry layout that adapts to different screen sizes
- Automatic Image Processing: On-the-fly image resizing with caching for multiple sizes
- High-DPI Support: Automatic @2x image generation for retina displays
- Metadata Extraction: EXIF data parsing including camera info, GPS coordinates, and capture dates
- Smart Caching: Persistent metadata caching and image cache with background refresh
- Multiple Format Support: Automatic WebP delivery for supported browsers with JPEG fallback, PNG support with transparency preservation
- Optional AVIF Support: Full HDR AVIF encoding/decoding with gain map preservation for HDR tone mapping (when built with AVIF feature)
- Color Profile Preservation: Full ICC profile support for JPEG, PNG, WebP, and AVIF, including Display P3
- Copyright Watermarking: Intelligent watermark placement with automatic text color selection
- Markdown Support: Folder descriptions and image captions via markdown files
- Hidden Folders: Hide folders from listings while keeping them accessible via direct URL
- New Image Highlighting: Configurable highlighting of recently modified images
- Multiple Blog Systems: Support for multiple independent blog/posts systems with markdown
- Dark Theme Code Blocks: Optimized code block styling for readability in dark theme
- Email-based Authentication: Secure passwordless login system with email verification links
- User Authentication: Optional user authentication system with rate limiting
- Email Provider Support: Pluggable email provider system with Amazon SES and null providers
- Cascading Static Files: Support for multiple static directories with file overlay precedence
- WebAuthn/Passkey Support: Modern passwordless authentication with biometric login
- Rust 1.89.0 or later (automatically managed by rust-toolchain.toml)
- DejaVuSans.ttf font file (required for watermarking)
git clone https://github.com/yourusername/tenrankai.git
cd tenrankai
# Default build with AVIF support
cargo build --release
# Build without AVIF for easier compilation (especially on Windows)
cargo build --release --no-default-featuresThe project includes a rust-toolchain.toml file that will automatically download and use Rust 1.89.0 when you run cargo commands. This ensures consistent builds across all development environments.
Tenrankai includes production-ready Docker support with optimized multi-stage builds.
# Pull from GitHub Container Registry (when available)
docker pull ghcr.io/theatrus/tenrankai:latest
# Or build locally
docker build -t tenrankai:latest .
# Run the container
docker run -d \
--name tenrankai \
-p 8080:8080 \
-v ./config.toml:/app/config.toml:ro \
-v ./photos:/app/photos:ro \
-v ./cache:/app/cache \
tenrankai:latest
# Or use docker-compose (recommended for complex setups)
# See docker-compose.example.yml for a complete example with all options
docker-compose up -dThe Docker image (~168 MB) includes full AVIF support with HDR and gain maps, using an optimized release build with a ~45 MB binary. The container runs as a non-root user for security.
The container expects these volumes:
/app/config.toml- Main configuration file (read-only recommended)/app/photos- Photo directories (read-only recommended)/app/cache- Image cache directory (read-write)/app/users.toml- Optional: User database for authentication/app/static- Optional: Custom static assets/app/templates- Optional: Custom templates (see below for override examples)
# Set custom log level
docker run -e RUST_LOG=debug ...
# Override configuration
docker run -e TENRANKAI_HOST=0.0.0.0 -e TENRANKAI_PORT=3000 ...- Container runs as non-root user (UID 1001)
- Mount config and photos as read-only (
:ro) - Never include secrets in the image
- Use environment variables or mounted files for sensitive data
Tenrankai supports multiple template directories with a precedence system, allowing you to override specific templates while using the built-in defaults.
Basic Override Example:
# Create custom templates directory
mkdir -p custom-templates/partials
# Create a custom header
cat > custom-templates/partials/_header.html.liquid << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>My Custom Gallery</title>
<style>
header { background-color: #2c3e50; color: white; }
</style>
</head>
<body>
<header>
<h1>My Photography Portfolio</h1>
</header>
<main>
EOF
# Update config.toml to use both directories
cat > config.toml << 'EOF'
[templates]
# Custom templates override built-in ones
directories = ["custom-templates", "templates"]
EOF
# Run with Docker
docker run -d \
-p 3000:3000 \
-v ./config.toml:/app/config.toml:ro \
-v ./custom-templates:/app/custom-templates:ro \
-v ./photos:/app/photos:ro \
-v ./cache:/app/cache \
tenrankai:latestAdvanced Theme Example:
# Create a complete theme override
mkdir -p themes/dark/{partials,pages,modules}
# Run with theme
docker run -d \
-p 3000:3000 \
-v ./config-with-theme.toml:/app/config.toml:ro \
-v ./themes:/app/themes:ro \
-v ./photos:/app/photos:ro \
-v ./cache:/app/cache \
tenrankai:latestWhere config-with-theme.toml contains:
[templates]
directories = ["themes/dark", "templates"]How It Works:
- Templates are searched in order - first match wins
- You only need to override the specific templates you want to customize
- Missing templates fall back to the next directory
- Partials follow the same rules, allowing mix-and-match
Common Override Patterns:
- Brand customization: Override
_header.html.liquidand_footer.html.liquid - Layout changes: Override
modules/gallery.html.liquid - Style updates: Override partials while keeping page structure
- A/B testing: Switch between template sets via config
AVIF Feature Flag: Tenrankai includes optional AVIF support that can be disabled for easier builds on platforms where AVIF dependencies are difficult to compile.
- With AVIF (Default): Full HDR AVIF support including gain maps, ICC profiles, and advanced color management
- Without AVIF: AVIF files are ignored, resulting in smaller binaries and simpler dependency requirements
Platform Recommendations:
- Linux/macOS: Use default build with AVIF support
- Windows: Consider using
--no-default-featuresif you encounter build issues with AVIF dependencies
Create a config.toml file in the project root. See config.example.toml for a complete example:
[server]
host = "127.0.0.1"
port = 3000
[app]
name = "My Gallery"
cookie_secret = "change-me-in-production-use-a-long-random-string" # Required: Used for signing auth cookies
base_url = "https://yourdomain.com"
user_database = "users.toml" # Optional: Enable user authentication
# Gallery configuration (multiple galleries supported)
[[galleries]]
name = "main"
url_prefix = "/gallery"
source_directory = "photos/main"
cache_directory = "cache/main"
images_per_page = 50
jpeg_quality = 85
webp_quality = 85.0
copyright_holder = "Your Name" # Optional: Add watermark to medium-sized images
[[galleries]]
name = "portfolio"
url_prefix = "/portfolio"
source_directory = "photos/portfolio"
cache_directory = "cache/portfolio"
images_per_page = 20
jpeg_quality = 90
webp_quality = 90.0
copyright_holder = "Your Portfolio Name"
[templates]
directory = "templates"
[static_files]
# Single directory (backward compatible)
directories = "static"
# OR multiple directories with precedence (first overrides later)
# directories = ["static-custom", "static"]
# Email configuration (required for login emails)
[email]
from_address = "[email protected]"
from_name = "My Gallery"
provider = "ses" # Options: "ses" for production, "null" for development
region = "us-east-1"
# Optional: specify AWS credentials (otherwise uses AWS SDK default chain)
# access_key_id = "your-access-key"
# secret_access_key = "your-secret-key"Gallery Configuration:
name: Unique identifier for the gallery (required for multiple galleries)url_prefix: URL path where the gallery will be accessible (e.g.,/gallery,/portfolio)source_directory: Path to your photo directorycache_directory: Where processed images and metadata are cachedimages_per_page: Number of images to display per pagenew_threshold_days: Days to consider an image "new" (remove to disable)pregenerate_cache: Pre-generate all image sizes on startup/refreshjpeg_quality: JPEG compression quality (1-100)webp_quality: WebP compression quality (0.0-100.0)approximate_dates_for_public: Show only month/year capture dates to non-authenticated usersgallery_template: Custom template for gallery pages (default: "modules/gallery.html.liquid")image_detail_template: Custom template for image detail pages (default: "modules/image_detail.html.liquid")copyright_holder: Copyright holder name for watermarking medium-sized images (optional)
Static Files Configuration:
directories: Static file directories (string or array)- Single directory:
directories = "static" - Multiple directories:
directories = ["static-custom", "static"] - Files in earlier directories override files in later directories
- Single directory:
Email Configuration:
from_address: Email address to send from (required)from_name: Display name for the sender (optional)reply_to: Reply-to address (optional)provider: Email provider to use ("ses" or "null")- Amazon SES Options:
region: AWS region where SES is configured (optional, uses SDK default)access_key_id: AWS access key (optional, uses SDK default chain)secret_access_key: AWS secret key (optional, uses SDK default chain)
- Null Provider Options:
- For development/testing: logs emails instead of sending them
- No additional configuration required
# Using default config.toml with AVIF support
cargo run --release
# Run without AVIF support (faster builds, especially on Windows)
cargo run --release --no-default-features
# With custom configuration
cargo run --release -- --config /path/to/config.toml
# Specify host and port
cargo run --release -- --host 0.0.0.0 --port 8080
# Enable debug logging
cargo run --release -- --log-level debug--config <path>: Path to configuration file (default: config.toml)--host <address>: Override configured host address--port <number>: Override configured port--log-level <level>: Set logging level (trace, debug, info, warn, error)--quit-after <seconds>: Auto-shutdown after specified seconds (useful for testing)
Analyze AVIF files to inspect their HDR properties, color spaces, and gain maps:
Note: This command is only available when building with AVIF support (default build).
# Basic analysis
cargo run -- avif-debug path/to/image.avif
# Detailed technical information
cargo run -- avif-debug path/to/image.avif --verboseThis command displays:
- Image dimensions and file size
- Color space properties (primaries, transfer characteristics, matrix coefficients)
- HDR detection results
- Gain map presence and parameters
- CLLI (Content Light Level Information) data
- ICC profile information
- Detailed HDR detection logic (with --verbose)
Tenrankai supports multiple independent gallery instances, each with its own:
- Source directory for photos
- URL prefix for web access
- Cache directory and settings
- Templates (customizable per gallery)
- Image quality and pagination settings
Example URLs for different galleries:
- Main gallery:
http://localhost:8080/gallery/ - Portfolio:
http://localhost:8080/portfolio/ - Archive:
http://localhost:8080/photos/archive/
photos/
├── vacation-2024/
│ ├── _folder.md # Folder description (markdown)
│ ├── IMG_001.jpg
│ ├── IMG_001.md # Image caption (markdown)
│ └── IMG_002.jpg
└── landscapes/
├── _folder.md
└── sunset.jpg
_folder.md: Place in any directory to add a description that appears at the top of the gallery page<imagename>.md: Create alongside any image to add a caption (e.g.,sunset.jpg→sunset.md)
Folders can use TOML front matter in _folder.md files for advanced configuration:
+++
hidden = true
title = "Private Collection"
require_auth = true
allowed_users = ["alice", "bob"]
+++
# Optional Markdown Content
This folder is hidden from gallery listings but remains accessible via direct URL.Configuration Options:
hidden = true: Hides the folder from gallery listings, previews, and counts (but allows direct access)title = "Custom Name": Override the folder display namerequire_auth = true: Require user authentication to access this folderallowed_users = ["user1", "user2"]: Restrict access to specific users (implies require_auth)
Hidden Folders:
- Do not appear in gallery navigation or listings
- Are excluded from gallery preview images and image counts
- Remain fully accessible if you know the direct URL
- Perfect for private collections or work-in-progress galleries
Access Control:
- Access restrictions are hierarchical (parent folder restrictions apply to children)
- Users must be authenticated to access folders with
require_auth = true - Only listed users can access folders with
allowed_usersspecified - Access control applies to folder browsing, image viewing, and API endpoints
Tenrankai includes a flexible posts/blog system that supports multiple independent collections:
Posts are markdown files with TOML front matter:
+++
title = "My Post Title"
summary = "A brief summary of the post"
date = "2024-08-24"
+++
# Post Content
Your markdown content here...Configure multiple independent post systems in your config.toml:
[[posts]]
name = "blog"
source_directory = "posts/blog"
url_prefix = "/blog"
posts_per_page = 20
refresh_interval_minutes = 30 # Auto-refresh posts every 30 minutes
[[posts]]
name = "stories"
source_directory = "posts/stories"
url_prefix = "/stories"
posts_per_page = 10Each system has its own:
- Source directory for markdown files
- URL prefix for web access
- Templates (customizable)
- Posts per page setting
- Optional automatic refresh interval for detecting new/changed posts
- Full CommonMark support with extensions (tables, strikethrough, footnotes)
- Automatic HTML generation from markdown
- Chronological sorting (newest first)
- Pagination support
- Subdirectory organization (URL reflects directory structure)
- Dynamic refresh via API
- Automatic periodic refresh (configurable interval)
- Individual post reloading when files change
- Dark theme optimized code blocks with syntax highlighting
- Responsive post layout for mobile and desktop
Tenrankai automatically generates multiple sizes for each image:
- Thumbnail: Small preview images for gallery grid
- Gallery: Standard viewing size used in the gallery layout
- Medium: Larger size with optional copyright watermark
- Large: Full quality (requires authentication)
All sizes support @2x variants for high-DPI displays.
Tenrankai preserves ICC color profiles and HDR metadata throughout the image processing pipeline:
- JPEG: Extracts and preserves ICC profiles from source images
- PNG: Extracts ICC profiles from iCCP chunks and preserves transparency
- WebP: Embeds ICC profiles using libwebp-sys WebPMux API
- AVIF (when built with AVIF feature): Full HDR support with advanced features:
- Preserves ICC profiles and HDR metadata (color primaries, transfer characteristics, CLLI)
- Supports gain maps for HDR/SDR tone mapping
- Automatically detects and preserves HDR content (BT.2020, Display P3, PQ/HLG)
- 10-bit encoding for HDR images
- Gain map preservation during image resizing
- Wide Gamut: Full support for Display P3, Adobe RGB, BT.2020, and other color spaces
- Watermarking: Color profiles and HDR metadata maintained even when adding copyright notices
This ensures accurate color reproduction across all devices and browsers that support color management. PNG images are always served as PNG to preserve transparency and avoid quality loss.
Tenrankai supports both email-based and WebAuthn/Passkey authentication for secure access:
-
User Management: Users are managed via a TOML file (
users.toml)- Copy
users.toml.exampletousers.toml - Add users with their username and email address
- No self-registration - admin manages all users
- When
user_databaseis not configured, the system runs without authentication
- Copy
-
Login Flow:
- User visits
/_loginand enters their username or email address - System sends an email with a secure login link
- User clicks the link to authenticate
- Session is maintained via secure HTTPOnly cookies
- Rate limiting prevents brute force attacks (5 attempts per 5 minutes per IP)
- User visits
-
User Administration:
# List all users cargo run -- user list # Add a new user cargo run -- user add --username alice --email [email protected] # Remove a user cargo run -- user remove --username alice # Update user email cargo run -- user update --username alice --email [email protected]
Tenrankai supports modern WebAuthn/Passkey authentication for passwordless login:
Prerequisites for WebAuthn:
- Configure
base_urlin yourconfig.toml(required for WebAuthn to work) - HTTPS connection (required by WebAuthn specification, except for localhost)
Features:
- Biometric Authentication: Fingerprint, face recognition, or hardware security keys
- Cross-Device Sync: Passkeys sync across devices via platform providers (iCloud, Google, etc.)
- Fallback Support: Email-based login remains available when WebAuthn is unavailable
- Multiple Passkeys: Users can register multiple passkeys per account
Passkey Management:
- After email login, users are prompted to enroll a passkey for faster future logins
- Users can view their profile and manage passkeys at
/_login/profile - Profile page shows username, email, and registered passkeys
- Passkeys can be removed through the profile interface
- New passkeys can be enrolled from the profile page
Login Flow with WebAuthn:
- User visits
/_loginand enters their username - If passkeys are available, user can choose passkey authentication or email fallback
- For passkey login: Browser prompts for biometric/hardware authentication
- For email login: Traditional email link is sent
- After successful email login, user is offered passkey enrollment
Email Configuration: Configure an email provider in your config.toml:
- Production: Use
provider = "ses"with Amazon SES for reliable email delivery - Development/Testing: Use
provider = "null"to log emails to console instead of sending them - No Configuration: Without email configuration, login URLs will be logged to the server console
To run Tenrankai without user authentication:
- Remove or comment out the
user_databaseline in your config.toml - The system will allow access to all features without login
- The user menu will not appear in the interface
This is useful for:
- Personal use on a private network
- Development and testing
- Public galleries where authentication isn't needed
GET /gallery- Gallery rootGET /gallery/{path}- Browse specific folderGET /gallery/image/{path}?size={size}- Get resized imageGET /gallery/detail/{path}- View image details pageGET /api/gallery/preview- Get random gallery preview images
GET /{prefix}- List posts with paginationGET /{prefix}/{slug}- View individual postPOST /api/posts/{name}/refresh- Refresh posts cache
GET /_login- Login pagePOST /_login/request- Request login email (accepts username or email)GET /_login/verify?token={token}- Verify login tokenGET /_login/logout- Logout and clear sessionGET /_login/profile- User profile and passkey management pageGET /api/verify- Check authentication status (JSON)
GET /_login/passkey-enrollment- Passkey enrollment page (post-login)POST /api/webauthn/check-passkeys- Check if user has registered passkeysPOST /api/webauthn/register/start- Start passkey registrationPOST /api/webauthn/register/finish/{reg_id}- Complete passkey registrationPOST /api/webauthn/authenticate/start- Start passkey authenticationPOST /api/webauthn/authenticate/finish/{auth_id}- Complete passkey authenticationGET /api/webauthn/passkeys- List user's registered passkeysDELETE /api/webauthn/passkeys/{passkey_id}- Delete a passkeyPUT /api/webauthn/passkeys/{passkey_id}/name- Rename a passkey
POST /api/refresh-static-versions- Refresh static file version cache (authenticated)
Tenrankai includes several performance optimizations:
- Persistent metadata caching reduces file system access
- Background cache refresh keeps data fresh without blocking requests
- Concurrent image processing with rate limiting
- Automatic cache pre-generation option for instant loading
- Browser-based caching headers for processed images
Templates are organized into three directories:
templates/
├── pages/ # Regular page templates
│ ├── index.html.liquid
│ ├── about.html.liquid
│ ├── contact.html.liquid
│ └── 404.html.liquid
├── modules/ # Module-specific templates
│ ├── gallery.html.liquid
│ ├── image_detail.html.liquid
│ ├── posts_index.html.liquid
│ └── post_detail.html.liquid
└── partials/ # Reusable template components
├── _header.html.liquid
├── _footer.html.liquid
└── _gallery_preview.html.liquid
All templates use the Liquid templating language and support includes for reusable components.
Tenrankai supports cascading static directories, allowing you to overlay custom files over default ones:
[static_files]
# Single directory (backward compatible)
directories = "static"
# OR multiple directories with precedence
directories = ["static-custom", "static-default"]When multiple directories are configured:
- Files in earlier directories take precedence over files in later directories
- If
logo.pngexists in bothstatic-customandstatic-default, the one fromstatic-customis served - Files unique to any directory are accessible normally
- Useful for:
- Custom themes that override default assets
- Environment-specific configurations
- Gradual migrations between asset sets
Place the following in one of your static directories:
DejaVuSans.ttf- Required for copyright watermarkingfavicon.svg- Used to generate favicon.ico and PNG variants (optional)robots.txt- Custom robots file (optional, defaults provided)- Any other static assets referenced in templates
The system will search all configured directories in order to find these files.
Control logging verbosity with the RUST_LOG environment variable or --log-level flag:
# Examples
RUST_LOG=debug cargo run
cargo run -- --log-level traceTenrankai is under active development with a comprehensive codebase and documentation.
- CONTRIBUTING.md: Development setup, code organization, and contribution guidelines
- API.md: Complete API reference with examples
- CHANGELOG.md: Detailed changelog of recent improvements
- README.md: This file - user guide and configuration reference
- ✅ WebAuthn/Passkey Authentication: Biometric and hardware key login support
- ✅ Gallery Access Control: Folder-level authentication and user restrictions
- ✅ User Profile Page: Centralized passkey management interface
- ✅ Cascading Static Directories: Multi-directory asset management with precedence
- ✅ Null Email Provider: Development-friendly email logging
- ✅ Enhanced Asset Management: Cache busting with automatic versioning
- ✅ Improved Authentication Flow: Return URL support and passkey enrollment
- ✅ Simplified TOML Database: Clean user database format using serde
- Additional email providers (SendGrid, SMTP, etc.)
- Full-text search across galleries and posts
- Video file support with thumbnail generation
- Tag-based filtering and organization
- User roles and permissions system
- Gallery-specific access controls
Contributions are welcome! Please:
- Read CONTRIBUTING.md for development setup
- Check existing issues or create new ones for bugs/features
- Follow the established code style and testing practices
- Submit pull requests with clear descriptions
- Async Rust: Built on Tokio with Axum web framework
- Thread-Safe Operations: Arc<RwLock> for concurrent access
- Comprehensive Testing: 95+ unit tests and integration tests (with AVIF), 76+ without AVIF
- Modular Design: Clean separation of concerns across modules
- Configuration-Driven: Flexible TOML-based configuration system
- Cross-Platform CI: Automated testing on Ubuntu, macOS, and Windows
- Ubuntu/macOS: Full feature builds with AVIF support
- Windows: Builds without AVIF for easier compilation
Licensed under the Apache License, Version 2.0. See LICENSE for details.