Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/syhily/yufan.me/llms.txt

Use this file to discover all available pages before exploring further.

yufan.me stores every piece of runtime configuration in the setting table as one JSONB row per section, identified by a scope string of the form blog.<section>. Sections save independently — when you click Save on the navigation settings page, only the blog.navigation row is written. Concurrent admin tabs editing different sections cannot overwrite each other’s changes, and a failed save in one section never rolls back another. All sections are edited from the admin console at /admin/settings/<section>. No environment variable change or server restart is required to apply updates.
Two sections — general and assets — have no auto-generated defaults and require user input at install time. The two-stage install gate at /admin/setup/settings collects these values and writes all 14 rows atomically before the public site becomes available.

Sections

DB scope: blog.generalThe core identity of the blog. Collected during the install gate and editable at /admin/settings/general.
FieldDescription
titleSite name, up to 120 characters
descriptionShort site description, up to 240 characters
websiteCanonical public URL (used in feeds and SEO meta)
keywordsUp to 20 keyword tags for SEO
author.nameAuthor display name
author.emailAuthor contact email
author.urlAuthor profile URL
localeBCP 47 language tag (e.g. zh-CN, en-US)
timeZoneIANA time zone name (e.g. Asia/Shanghai, UTC)
timeFormatDate format token string consumed by formatLocalDate
initialYearYear the blog launched, shown in copyright notices
icpNoOptional ICP filing number (China regulatory)
moeIcpNoOptional Moe ICP filing number
This section has defaults: null — it must be filled in during setup.
DB scope: blog.assetsControls the public asset CDN host, S3-compatible storage credentials, and image upload policy. Editable at /admin/settings/assets.
FieldDescription
asset.hostCDN hostname (e.g. cdn.example.com). Used to construct public image and music URLs
asset.schemehttp or https
storage.enabledToggle S3 uploads on or off. Defaults to false on fresh installs
storage.endpointS3-compatible endpoint URL
storage.regionBucket region
storage.bucketBucket name
storage.accessKeyIdS3 access key
storage.secretAccessKeyS3 secret key (send undefined to keep the stored value)
storage.forcePathStyleSet true for MinIO and other path-style endpoints
storage.urlTemplateCustom public URL template for the bucket
upload.maxBytesMaximum upload size in bytes (1 KB – 50 MB)
upload.jpegQualityJPEG re-encode quality (40–100)
When storage.enabled is false, uploads are refused with a 503 response and the admin image library is read-only. Previously uploaded images still resolve against the stored publicBaseUrl. Flipping the toggle back on does not require re-entering credentials.This section has defaults: nullasset.host must be supplied during setup.
DB scope: blog.contentControls how content is paginated, how the RSS/Atom feed is generated, and how posts are ordered.
FieldDescription
pagination.postsPosts per page on the home listing (1–100, default 10)
pagination.categoryPosts per page on category listings (default 10)
pagination.tagsPosts per page on tag listings (default 10)
pagination.searchResults per page in search (default 10)
feed.fulltrue to include full post body in feeds; false for excerpt only
feed.sizeNumber of posts in the feed (1–100, default 20)
post.sortSort direction: asc or desc (default desc)
post.sortBySort field: publishedAt or updatedAt
post.featureEnabledEnable featured post pinning on the home page
footnotes.sectionTitleHeading text for the footnotes section (default 尾声礼记)
DB scope: blog.commentsControls comment display and the anonymous commenter token lifecycle.
FieldDescription
comments.sizeComments per page (1–100, default 10)
comments.avatar.mirrorGravatar mirror URL (default https://www.gravatar.com/avatar)
comments.avatar.sizeAvatar pixel size (16–512, default 80)
comments.tokenTtlSecondsSeconds before an anonymous commenter token expires (60–86400, default 1800)
To use a regional Gravatar mirror (e.g. for improved latency), set comments.avatar.mirror to the mirror hostname such as https://gravatar.loli.net/avatar.
DB scope: blog.seoControls which heading levels appear in the post table of contents and the pixel dimensions of generated Open Graph images.
FieldDescription
toc.minHeadingLevelMinimum heading level included in the TOC (1–6, default 2)
toc.maxHeadingLevelMaximum heading level included in the TOC (1–6, default 4)
og.widthOG image canvas width in pixels (600–4096, default 1200)
og.heightOG image canvas height in pixels (315–4096, default 630)
OG image dimensions take effect on the next generation request. The X/Facebook recommended minimum is 600×315; Facebook recommends at least 1200×630 for feed display.
DB scope: blog.mailConfigures the outgoing mail integration. yufan.me uses Zeabur ZSend as its mail transport.
FieldDescription
mail.enabledToggle outgoing mail on or off (default false)
mail.hostZSend API hostname, without scheme (default api.zeabur.com)
mail.apiKeyZSend API key. Send undefined to keep the stored value without re-pasting
mail.senderVerified sender email address (must be a valid email)
Mail is disabled by default. No outgoing mail flows until you supply a valid ZSend host, API key, and sender address and flip enabled to true. Use the Send test email button on the settings page to verify the configuration before enabling it in production.
DB scope: blog.cacheConfigures the Redis key prefix and TTL for each cache bucket. Each bucket’s prefix must end with : and must not collide with another bucket’s prefix or with the reserved system prefixes (session:, rate-limit:, avatar-status:).
BucketDefault prefixDefault TTLCaches
ogog:24 hoursGenerated Open Graph images
calendarcalendar:24 hoursGenerated calendar SVGs
avataravatar:24 hoursProxied Gravatar/QQ avatars
imageMetaimage-meta:1 hourImage row lookups (storagePath → ImageRow)
embeddingSearchembedding-search:7 daysOpenAI embedding vectors for semantic search
searchResultsearch-result:1 hourSearch result pages
TTL bounds are 1 hour (minimum) to 30 days (maximum). Changing a prefix or TTL takes effect immediately; the old keys are orphaned in Redis until they expire naturally or you flush the bucket from the settings page.
DB scope: blog.rateLimitConfigures the sliding-window rate limit for each protected surface. Every bucket has a windowSeconds (60 s – 24 h) and a maxAttempts (1–1000) field.
BucketDefault windowDefault max attemptsProtects
signInIp30 min5Login form, per source IP
commentPostIp1 hour12Anonymous comment submission, per source IP
commentPostEmail1 hour8Anonymous comment submission, per email address
likeIncreaseIp1 hour30Post like button, per source IP
inviteIp1 hour10Admin invite flow, per source IP
inviteEmail1 hour5Admin invite flow, per email address
passwordResetIp1 hour5Password reset, per source IP
passwordResetEmail1 hour5Password reset, per email address
passwordResetTarget1 hour3Password reset, per target user
The defaults mirror the historical hard-coded values, so upgrading an existing deployment does not change observed behaviour until the admin explicitly tunes the caps.
DB scope: blog.searchControls whether search is enabled and how it operates.
FieldDescription
search.enabledToggle site search on or off (default false)
search.modelike for SQL LIKE full-text search, vector for OpenAI vector/embedding-based semantic search
search.endpointOpenAI-compatible API endpoint. Leave empty to use the official OpenAI endpoint
search.apiKeyAPI key for the embedding provider. Send undefined to keep the stored value
search.modelEmbedding model name (default text-embedding-3-small)
search.similarityThresholdCosine similarity threshold for vector search results (0–1, default 0.5)
like mode requires no external service and works on a fresh install. vector mode requires a valid API key and pre-computed embeddings; the embedding pipeline runs automatically when posts are published or updated while vector mode is active.
DB scope: blog.fontsConfigures the font URLs used by the OG image renderer, the calendar SVG generator, and the public site CSS.
FieldDescription
og.urlFont file URL for the OG image canvas renderer. Empty string disables custom font
calendar.urlFont file URL for the calendar SVG renderer. Empty string disables custom font
globalCssArray of up to 8 CSS stylesheet URLs loaded on every public page
postCssArray of up to 8 CSS stylesheet URLs loaded only on post detail pages
All URLs must be absolute. An empty string or empty array falls back to system fonts silently — the home page, archives, and OG images all render without throwing even before any font URLs are configured. Add font URLs from /admin/settings/fonts when you are ready to apply custom typography.
DB scope: blog.backupConfigures automated pg_dump backups uploaded to the configured S3 bucket.
FieldDescription
scheduled.enabledToggle scheduled backups on or off (default false)
scheduled.frequencydaily, weekly, or monthly
scheduled.hourUTC hour to run the backup (0–23, default 3)
scheduled.minuteMinute past the hour: 0 or 30 (default 0)
scheduled.dayOfWeekRequired when frequency is weekly (1 = Monday, 7 = Sunday)
scheduled.dayOfMonthRequired when frequency is monthly (1–28)
retention.enabledAuto-delete old backups from S3 (default true)
retention.daysDays to keep backups before deletion (1–365, default 30)
Backups require S3 storage to be enabled and configured in the assets section. The audit_log table is excluded from pg_dump backups; audit archive files are managed separately.
Two sections — general and assets — have defaults: null in the section registry. They have no auto-generated seed values and require user input at install time. The install gate at /admin/setup/settings collects the site name, description, author details, and asset CDN host before writing all 14 rows atomically. Every other section ships with conservative defaults (empty arrays, disabled integrations, minimal TTLs) so the public site renders immediately after setup without requiring the admin to visit each settings tab.

Build docs developers (and LLMs) love