Core Concepts

Before diving into detailed configuration, it’s important to understand how Tenrankai works. This guide covers the fundamental concepts that make Tenrankai unique.

File-Based Philosophy

Tenrankai operates entirely on files and folders - no database required. This means:

  • Drop files to publish: Add images to a folder, they appear in your gallery
  • Edit files to update: Change a markdown file, your content updates
  • Delete files to remove: Remove an image, it’s gone from the gallery
  • Version control friendly: Track all changes with git
  • Easy backups: Just copy your folders

This approach works perfectly with file synchronization tools like SyncThing, allowing you to edit content on any device and have it automatically deployed.

Directory Structure

A typical Tenrankai site looks like this:

my-gallery/
├── config.toml              # Bootstrap configuration (server, email)
├── config.d/                # Site configuration (ConfigStorage)
│   └── sites/
│       └── default/
│           ├── site.toml        # Site settings
│           ├── permissions.toml # Roles and access control
│           ├── galleries/
│           │   └── main.toml    # Gallery configurations
│           └── posts/
│               ├── blog.toml    # Blog configuration
│               └── docs.toml    # Documentation configuration
├── users.toml               # User database (if authentication enabled)
├── templates/               # Liquid templates
│   ├── pages/              # Full page templates
│   ├── modules/            # Reusable components
│   └── partials/           # Shared fragments
├── static/                  # CSS, JavaScript, fonts, images
├── photos/                  # Your gallery images
│   ├── 2026/
│   │   ├── vacation/
│   │   │   ├── IMG_001.jpg
│   │   │   ├── IMG_001.jpg.md    # Optional metadata
│   │   │   └── _folder.md        # Folder description
│   │   └── portraits/
│   └── _folder.md
├── posts/                   # Markdown content
│   ├── blog/               # Blog posts
│   └── docs/               # Documentation
└── cache/                   # Generated images (auto-created)

Two-Tier Configuration

Tenrankai uses a two-tier configuration system:

  1. Bootstrap config (config.toml): Server settings, email, OpenAI - static settings that rarely change
  2. ConfigStorage (config.d/): Site-specific configuration (galleries, posts, permissions) that can be managed via CLI or Admin UI (recommended for all new installations)
# Use default config.toml
tenrankai serve

# Use a specific config file
tenrankai serve --config production.toml

Quick Setup with CLI

The easiest way to get started is using the CLI:

# Initialize ConfigStorage directory with a default site
tenrankai config init config.d

# Add galleries and posts to your site
tenrankai config add-gallery photos --site default --source photos --url-prefix /gallery
tenrankai config add-posts blog --site default --source posts/blog --url-prefix /blog

Bootstrap Configuration (config.toml)

The bootstrap config contains server-level settings:

[server]
host = "127.0.0.1"
port = 3000

[app]
name = "My Gallery"
cookie_secret = "CHANGE_ME_generate_with_openssl_rand_base64_32"
config_storage = "config.d"  # Path to ConfigStorage directory

# Optional: Email for authentication
# [email]
# provider = "null"
# from_address = "noreply@example.com"

Site Configuration (ConfigStorage)

Site-specific configuration lives in config.d/sites/default/:

site.toml - Site settings:

hostnames = ["localhost", "photos.example.com"]
templates = ["templates"]
static_files = ["static"]
base_url = "https://photos.example.com"
# user_database = "users.toml"  # Enables authentication

galleries/main.toml - Gallery configuration:

name = "main"
url_prefix = "/gallery"
source_directory = "photos"
cache_directory = "cache/main"

permissions.toml - Roles and access control:

public_role = "viewer"
[roles.viewer]
permissions = { can_view = true }

Server Configuration

The [server] section controls network settings:

[server]
host = "127.0.0.1"  # IP address to bind to
port = 3000         # Port number
SettingDescriptionCommon Values
hostIP address to listen on127.0.0.1 (local only), 0.0.0.0 (all interfaces)
portPort number3000 (dev), 8080 (behind proxy)

For production, bind to 127.0.0.1 and use a reverse proxy (nginx, Caddy) for HTTPS.

Application Configuration

The [app] section in the bootstrap config contains global settings:

[app]
name = "My Photo Gallery"
cookie_secret = "CHANGE_ME_generate_with_openssl_rand_base64_32"
config_storage = "config.d"  # Path to ConfigStorage directory
SettingRequiredDescription
nameYesSite name (appears in titles, emails)
cookie_secretYesSession signing secret (generate with openssl rand -base64 32)
log_levelNotrace, debug, info, warn, error (default: info)
config_storageNoPath to ConfigStorage directory (recommended)

Site-specific settings like base_url and user_database are configured in config.d/sites/default/site.toml.

Generating a Cookie Secret

# Generate a secure random secret
openssl rand -base64 32

CLI Configuration Commands

Manage your configuration without editing files:

# Initialize ConfigStorage
tenrankai config init config.d

# List and manage sites
tenrankai config list-sites
tenrankai config add-site photos --hostname photos.example.com

# Manage galleries
tenrankai config list-galleries default
tenrankai config add-gallery portfolio --site default --source portfolio --url-prefix /portfolio

# Manage posts
tenrankai config list-posts default
tenrankai config add-posts blog --site default --source posts/blog --url-prefix /blog

Templates and Static Files

Templates and static files support cascading directories, configured in site.toml:

# config.d/sites/default/site.toml
templates = ["templates-custom", "templates"]
static_files = ["static-custom", "static"]

How cascading works:

  1. Tenrankai looks for each file in directories left-to-right
  2. First match wins
  3. Override specific files without copying everything

This lets you customize individual templates or CSS files while keeping the defaults for everything else.

Galleries

Galleries are the core of Tenrankai. Each gallery is an independent collection of images with its own URL, settings, and permissions. Each gallery is configured as a separate TOML file in config.d/sites/<site>/galleries/:

# config.d/sites/default/galleries/main.toml
name = "main"                    # Unique identifier
url_prefix = "/gallery"          # URL path
source_directory = "photos"      # Where images are stored
cache_directory = "cache/main"   # Where processed images go

Add multiple galleries by creating additional TOML files:

# Via CLI
tenrankai config add-gallery portfolio --site default --source portfolio --url-prefix /
tenrankai config add-gallery family --site default --source family-photos --url-prefix /family

Or create the files directly:

# config.d/sites/default/galleries/portfolio.toml
name = "portfolio"
url_prefix = "/"
source_directory = "portfolio"
cache_directory = "cache/portfolio"

See Gallery Setup for complete gallery configuration.

Posts (Blog/Documentation)

Posts are markdown files that become pages on your site. Each posts collection is configured as a TOML file in config.d/sites/<site>/posts/:

# config.d/sites/default/posts/blog.toml
name = "blog"
source_directory = "posts/blog"
url_prefix = "/blog"
posts_per_page = 10
refresh_interval_minutes = 30
# config.d/sites/default/posts/docs.toml
name = "docs"
source_directory = "posts/docs"
url_prefix = "/docs"
posts_per_page = 20

Post Format

Posts use TOML frontmatter:

+++
title = "My First Post"
summary = "A brief description"
date = "2026-01-15"
categories = ["Travel", "Photo Gear"]          # optional
hero_image = "gallery:main:vacation/beach.jpg" # optional
+++

# My First Post

Content goes here in **markdown** format.

Required fields are title, summary, and date (either YYYY-MM-DD or full RFC 3339). Posts are sorted by date (newest first) and automatically paginated.

Categories

The optional categories list assigns one or more labels to a post. Categories appear as clickable chips on the index and on each post, and the index page grows a filter bar with per-category post counts. Each category gets its own index page at a URL-safe slug of the label (/blog/category/photo-gear), and pagination preserves the active filter. Category pages for publicly visible posts are included in the sitemap.

Category Options

Categories work with zero configuration, but an optional _categories.md file at the posts source root can declare per-category options (keyed by the category’s slug):

+++
[categories.archive]
name = "Archive"
description = "Older posts kept for reference"
archive = true

[categories.travel]
weight = 1
+++
  • name — canonical display label, overriding whatever spelling appears in post frontmatter. Used on chips, filter notes, and feed titles.
  • description — shown under the category page heading, and used as the category feed’s description and the page’s meta description.
  • archive — posts carrying this category are hidden from the unfiltered index and the main RSS feed. They still list on every category page they belong to (the archive category’s page acts as the archive view), remain reachable at their permalinks, and stay in the sitemap. Archiving a post is just adding the label.
  • weight — chip ordering in the filter bar: lower weights sort first, undeclared categories follow alphabetically, and archive categories always render last after a visual separator.

Undeclared categories keep the defaults, so the file only needs entries for categories you want to customize.

RSS Feeds

Every posts system serves RSS 2.0 feeds automatically:

  • /blog/feed.xml — the 20 most recent posts
  • /blog/category/photo-gear/feed.xml — the same, filtered to one category

Feeds include the full post content, honor the posts permission system (a restricted post only appears when the feed is fetched with an authorized session), and are advertised via <link rel="alternate"> tags and a subscribe link at the bottom of the posts index.

Post pages also emit article Open Graph metadata for rich link previews: article:published_time, article:modified_time, article:author, article:section (the first category), and one article:tag per category.

Hero Images

The optional hero_image sets the image shown on the post’s index card and used for social media previews (og:image). It accepts either a plain URL or a gallery reference in the form gallery:<gallery-name>:<path/to/image.jpg>. When omitted, the first image found in the post content is used automatically. A gallery-reference hero is clickable on the post page, linking through to the image’s gallery detail page.

Embedding Gallery Images

Post content can embed images from any configured gallery using an image tag whose alt text is a gallery reference and whose URL is a size hint (thumbnail, gallery, medium, or large), optionally followed by comma-separated options:

![gallery:main:vacation/sunset.jpg](gallery)
![gallery:main:vacation/sunset.jpg](medium,details)

The image links to its gallery detail page and is served in the requested size with lazy loading. The details option adds a hover card showing the image’s description and technical details — camera, lens, exposure, and the astronomical fields (telescope, filters, integration time, RA/Dec) when present.

Post authors with can_edit_content don’t need to type references by hand: the web editor’s Browse… button (hero) and Gallery image toolbar button (content) open a gallery browser and insert the reference, with size and details options for embeds.

The Index Page and Sharing

The posts index renders as a flowing card grid: the newest post appears as a full-width featured card, and cards show hero images, category labels, dates, estimated reading time, and summaries. Each post page includes a share bar with native share support, Bluesky, X, Facebook, LinkedIn, Mastodon (your instance is remembered locally), email, and copy-link buttons, plus Open Graph and Twitter Card metadata for rich link previews.

Embedding Posts on Pages

Any static page template can embed a recent-posts summary block, in the same spirit as the embeddable gallery preview:

{% include "_posts_preview.html.liquid" posts_name: "blog", posts_url: "/blog", heading: "Latest Posts" %}

Optional parameters: count (default 3, max 12), category (a category slug to filter by), and variant: "list" for a super-compact title + date list instead of summary cards. The block fetches posts client-side so it respects the viewer’s permissions, excludes archived posts (unless filtered to an archive category), hides itself when there are no matching posts, and always renders a “View All Posts” link that works without JavaScript.

The gallery preview partial accepts an optional count parameter as well (default 6, max 20), and multiple preview blocks of either kind can share a page.

Image Metadata

Tenrankai reads image metadata from multiple sources:

  1. Markdown sidecar files (highest priority)

    • IMG_001.jpg.md or IMG_001.md
    • TOML frontmatter + markdown description
  2. XMP sidecar files

    • IMG_001.jpg.xmp
    • Adobe Lightroom compatible
  3. EXIF data (lowest priority)

    • Embedded in the image file
    • Camera, lens, GPS, timestamps

Example markdown sidecar (vacation/beach.jpg.md):

+++
title = "Sunset at the Beach"
description = "Golden hour at Santa Monica"
tags = ["sunset", "beach", "california"]
+++

Extended description with **markdown** formatting.

Astrophotography Metadata and Sky Maps

Sidecar frontmatter can carry astronomical fields — telescope, mount, filters, total_exposure_time (hours), ra, dec, and additional_details. When an image has both ra and dec (e.g. ra = "05h 34m 32s", dec = "+22° 00' 52\""), its detail page renders a Sky Position card: an all-sky chart with the brightest stars and a crosshair at the target, plus links opening Aladin Lite and SIMBAD at those coordinates. Coordinates parse from sexagesimal hour or degree forms as well as plain decimal degrees. Like the rest of the technical metadata, the card is shown to viewers with can_see_technical_details.

Plate Solving and Object Overlays

With seiza data files configured in the bootstrap config.toml, astronomical images are plate-solved on demand — an exact WCS is fitted from the star field itself:

[astro]
star_data = "/var/lib/tenrankai/astro/stars-deep-gaia17.bin"  # Required
blind_index = "/var/lib/tenrankai/astro/blind-gaia16.idx"     # Blind solving
object_data = "/var/lib/tenrankai/astro/objects.bin"          # Overlay objects

Images with ra/dec in their sidecar are solved from that hint; images in a folder marked astro = true can be blind-solved without any coordinates. Solved images gain an Objects toggle overlaying labeled deep-sky objects and named stars, live supernova markers, and comets and asteroids placed at the image’s capture time. Overlays follow into zoomed views, post embeds, and post heroes.

See the Astrophotography guide for the full setup: catalog files, transient refresh, overlay regeneration, and the API.

Folder Descriptions

Add _folder.md to any folder to provide a title and description:

+++
title = "Summer Vacation 2026"
hidden = false
+++

Photos from our trip to California.
SettingDescription
titleDisplay name for the folder
hiddenIf true, folder is accessible but not listed
hide_technical_detailsIf true, hides camera/EXIF info
[permissions]Folder-specific access control

Caching

Tenrankai automatically caches processed images (resized, watermarked, converted). The cache:

  • Is created automatically in the configured cache_directory
  • Persists across restarts
  • Can be pre-generated on startup
  • Supports both local filesystem and S3 storage

Cache Queue

Control background cache generation with the [cache_queue] section in config.toml:

[cache_queue]
enabled = false        # Enable background cache processing (default: false)
buffer_size = 1000     # Number of items to buffer in the queue (default: 1000)
concurrency = 4        # Number of concurrent cache generation tasks (default: number of CPU cores)

When enabled, cache entries are generated in the background as images are requested, using the configured concurrency level to balance performance with system resources.

Cache CLI

You generally don’t need to manage the cache manually, but CLI commands are available:

# View cache coverage report
tenrankai cache report -g photos

# Clean outdated cache files
tenrankai cache cleanup -g photos

Admin UI

Tenrankai includes a built-in administration interface at /_admin/ for managing your site without editing configuration files.

Features:

  • User Management: Create, delete, and invite users
  • Gallery Viewer: See gallery configurations and permissions
  • Role Viewer: View built-in roles and their permissions
  • Site Configuration: Manage site settings via API

Access Requirements:

  • Must be authenticated
  • Must have owner_access permission in any gallery

The Admin UI is a React-based single-page application that communicates with the Admin API.

Next Steps

Now that you understand the basics:

  1. Gallery Setup - Configure image processing, sizes, and formats
  2. Authentication - Set up user accounts and login
  3. Permissions - Control who can see what
  4. Advanced Features - Admin UI, S3 storage, multi-site hosting
  5. Astrophotography - Plate solving and object overlays