- Go 75.2%
- JavaScript 7.6%
- HTML 5.3%
- CSS 5.3%
- YAML 3%
- Other 3.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| ansible | ||
| contrib | ||
| internal | ||
| tests/browser | ||
| .env.example | ||
| .envrc | ||
| .gitattributes | ||
| .gitignore | ||
| .golangci.yaml | ||
| build.sh | ||
| compose.example.yaml | ||
| config.go | ||
| config_tags_test.go | ||
| config_test.go | ||
| Dockerfile | ||
| flake.lock | ||
| flake.nix | ||
| go.mod | ||
| go.sum | ||
| instances.json | ||
| main.go | ||
| Makefile | ||
| README.md | ||
| renovate.json | ||
Gelbooru-Go
A lightweight private frontend for Gelbooru written in Go with caching and
security features. Besides the Go standard library it depends only on
golang.org/x/net, golang.org/x/sync and the otter cache, keeping
dependencies minimal. Every page works without
JavaScript; the one small script (internal/web/static/app.js) adds colored
tag autocomplete and ←/→ keyboard navigation.
Opening a post from a gallery page adds Previous / Next links that follow the order of that search, across result pages.
Instances
Public instances of Gelbooru-Go:
| Country | Address | Rating |
|---|---|---|
| Germany | gel.bloat.cat | General |
| Chile | gel.nadeko.net | General |
Requirements
- Latest version of Go
Deployment
Docker Compose
Copy compose.example.yaml to compose.yaml and .env.example to .env, then
edit them as needed:
cp compose.example.yaml compose.yaml
cp .env.example .env
Edit .env file with your configuration. You can obtain your API key and user
ID from
Gelbooru Account Options.
Then run:
docker compose up -d
Without Docker
Clone the repository:
git clone https://git.bloat.cat/vlnst/gelbooru-go && cd gelbooru-go
Set environment variables and run:
export API_KEY=your_api_key_here
export USER_ID=your_user_id_here
export PORT=3000
export POSTS_LIMIT=30
export RATING=general
export IMAGE_CACHE_MB=128
make run
You can automate this using systemd or another init system.
Behind Anubis
Enable Open Graph passthrough in the site's policy (openGraph.enabled: true). Full-image mode returns the same HTML to browsers and crawlers, including real title/artist text and full-image metadata. It also advertises /oembed?url=<post URL>&format=json and /users/<artist-id>/statuses/<id>; Discord can retrieve the accompanying /api/v1/statuses/<id> response. These read-only compatibility endpoints mirror the preview format used by PixivFE and are enabled only with FULL_IMAGE_EMBEDS=true. Artist identities are derived from credited artist tags, not uploader IDs.
Preview crawlers must also be able to retrieve the proxied image. These example rules allow common preview user agents only on post, media, and the preview discovery endpoints, and Discord's media proxy (which plays embed videos with a browser-like user agent) only on embed media URLs. Add them before generic challenge/deny rules in a policy scoped to this site. User-agent matching is spoofable: a client faking one of these user agents also skips the challenge for /proxy. Other requests retain the configured Anubis protection. Existing previews may remain cached by the sharing app.
Configuration
Required Variables
API_KEY- Your Gelbooru API key (obtain from Account Options)USER_ID- Your Gelbooru user ID (obtain from Account Options)
Optional Variables
PORT- Server port (default:3000)POSTS_LIMIT- Posts per page, 1-100 (default:30)RATING- Maximum allowed rating (default:general)IMAGE_CACHE_MB- Memory budget for the image cache in megabytes,0disables it (default:128)FULL_IMAGE_EMBEDS- Use PixivFE-style rich previews: original image, post title, credited artist, rating, score, and dimensions, with oEmbed and Mastodon-compatible discovery (default:false). Video posts include video metadata and a preview image. Embed appearance depends on the sharing app.TAGS_ON_LEFT- Show grouped post tags in a left sidebar instead of the collapsed Tags section (default:false). On narrow screens, the sidebar appears below the image.
Browser preferences
The search field suggests tags as you type; use arrow keys and Enter or click to select. Negative tags retain their - prefix. Suggestions are fetched through this server.
Use -tag in the search field to exclude posts with that tag. JavaScript is required for autocomplete and arrow-key navigation. Search, filters, pagination, media, and grouped tag links are server-rendered and work with JavaScript disabled, regardless of FULL_IMAGE_EMBEDS or TAGS_ON_LEFT. JavaScript-only controls appear after the script initializes. A reverse proxy such as Anubis may independently require JavaScript to pass its challenge.
Post tags are grouped and colored by Artist, Copyright, Character, General, and Metadata, then alphabetized within each group. Categories share the cached upstream post-page response used for comments and artist metadata. Unavailable or unknown categories appear under Other, preserving every tag.
Rating System
Ratings follow a hierarchy: general → sensitive → questionable →
explicit
RATING sets the highest rating this instance lets visitors select; it does
not choose the ratings shown in every search. For example, RATING=explicit
lets visitors choose general, sensitive, questionable, and explicit, while
RATING=questionable makes only general, sensitive, and questionable available.
Visitors can check one or more ratings in Ratings. The gallery includes
only those ratings. The selection is saved in a cookie and included in page
links, so it stays active while browsing and can be shared. A new visitor starts
with general selected. With RATING=general there is nothing to choose, so the
Ratings group is not shown at all.
Performance & Security
Caching
- API responses: Cached for 5 minutes
- Images: Cached for 60 minutes
- Large files (>10MB) and videos are streamed instead of cached
Security Features
- Security headers: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
- Request deduplication: Multiple concurrent requests for the same post share a single API call (singleflight pattern)
- URL validation: Proxy endpoint only allows Gelbooru domains
- Content-Type validation: Only allowed media types are served
- Size limits: Maximum image size 50MB, URL length 2048 chars, tags length 500 chars
Contributing
This project follows Semantic Versioning and uses Conventional Commits for commit messages.
Commit Message Format
Follow the Conventional Commits specification:
fix:- Bug fixes (patch version bump)feat:- New features (minor version bump)feat!:orfix!:- Breaking changes (major version bump)docs:- Documentation changesstyle:- Code style changes (formatting, etc.)refactor:- Code refactoringperf:- Performance improvementstest:- Test additions or changeschore:- Other changes (build, config, etc.)
Examples:
fix: resolve memory leak in cache cleanupfeat: add support for custom rating filtersfeat!: change API response format
Code Quality
Before submitting code:
- Format code:
go fmt - Run linter:
golangci-lint run