- Dart 57.9%
- C# 41.1%
- CMake 0.4%
- C++ 0.3%
- Dockerfile 0.2%
Fixes #81. Docs-only outcome of the 2026-08-16 production incident (uploads failing with `UnauthorizedAccessException` while reads worked, see issue for the full timeline). Production's three external media volumes are CIFS-backed (`type=cifs` local volumes mounting `//192.168.68.80/Doujinshi/…`) and were created without `uid=`/`gid=` — so everything presented as `root:root` with fake 0755 modes, `chown` was a silent no-op, and every write under uid 1654 failed. The docs never mentioned this layout or its ownership rules. - **§4.1**: document the CIFS volume layout production actually uses, with `uid=1654,gid=1654,file_mode=0644,dir_mode=0755` pinned in the mount options; the bind-mount variant stays as the chown-repairable alternative. - **§8.1 item 4**: CIFS-backed volumes cannot be chowned — ownership is fixed at mount time. Fingerprint (reads OK, writes fail, chown exits 0 and changes nothing) + the recreate-and-redeploy fix, cross-linked to §4.1. - **Troubleshooting**: new row for the read-works-write-fails fingerprint pointing at §8.1 item 4. The fix procedure itself was executed and verified in production earlier today (volume recreation + stack redeploy; upload, thumbnails, and backup paths confirmed working) — this PR writes down what actually works. Co-authored-by: bjoern <bjoern@kagaku.eu> Reviewed-on: #82 |
||
|---|---|---|
| .github | ||
| app | ||
| backend | ||
| deploy | ||
| docs | ||
| vendor | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| .gitmodules | ||
| README.md | ||
Doujin Manager
A self-hosted doujinshi collection manager with a .NET backend and a Flutter desktop app.
The backend owns canonical storage, metadata, search, and image handling. It exposes a resource-oriented REST API with lightweight hypermedia, backed by SQLite and UUID-based image storage. The Flutter app is the rich desktop client for browsing, managing, and editing the collection.
Current status
The backend server is complete and deployed. It provides a fully functional REST API covering doujin management, language variants, page/chapter organization, image upload (individual files and ZIPs), structured search, metadata entities (tags, people, circles, characters, series), and database backups.
The Flutter desktop app is feature-complete for the MVP scope: library with filtered search and autocomplete, doujin detail view with variant tabs and user rating, metadata entity management for all five types, a full Editor for doujins (metadata, associations, variants, chapters, uploads, page grid), and an immersive reader.
What works — backend
- Full CRUD for doujins, titles, variants, chapters, pages, and all five metadata entity types
- Doujin associations: tags, people (with role), circles, characters, series
- Image upload via individual files or ZIP archives, optionally targeted at a chapter
- Page management: reorder (variant-global), move between chapters, delete, set as cover
- Automatic thumbnail generation (SkiaSharp)
- UUID-based sharded image storage on the filesystem
- Structured boolean search (include/exclude tags, language OR, person, circle, character, series, rating)
- SQLite persistence via EF Core with auto-migrating startup
- Static bearer token authentication
- REST API with hypermedia response envelope (
data,links,actions) - OpenAPI spec at
/openapi/v1.jsonwith Scalar API reference UI at/scalar - Database backup/restore via API (
POST /api/backups) - Docker deployment with health checks
- CI/CD pipeline: build, test, publish to Forgejo container registry
What works — Flutter app
- Library page with smart filter bar: token-based filtering with autocomplete over names and aliases of all five metadata types, plus rating
- Doujin detail view: variant tabs with page thumbnails (collapsible chapters), editable user rating, associations, cover
- Immersive reader (the only rail-less screen): single full-res page on black, reading-direction-aware keyboard/mouse/click navigation, pan/zoom, auto-hiding overlay with chapter jump, page slider, and rating; adjacent pages precached
- Metadata management page: master-detail editors for tags, people, circles, characters, and series (name, description, aliases)
- Editor page (master-detail): searchable doujin list, tabbed editing per doujin
- Metadata tab: titles (add/edit/remove with language + kind), description, reading direction, cover preview, Ctrl+S save
- Associations tab: attach/detach all five types with alias-aware autocomplete; people carry a role
- Variants & Pages tab: variant CRUD with default flag, chapter chips, upload panel (multi-image picker, folder picker with subfolders→chapters mapping, ZIP), sequential upload queue with progress/retry, and a virtualized page grid with collapsible chapter sections, multi-select, drag reorder, context menu (move to chapter, set as cover, delete)
- AI assistant (docked side panel, ADR 0024): chat agent over the user's own OpenRouter account that can search the library, read doujins, navigate the app, research on the web (search → fetch → budgeted headless browser), look at covers/pages (vision or blind-mode captioning), and edit metadata — every database write requires inline user approval. Recurring workflows are packaged as loadable skills (built-in + user markdown files under
~/.config/doujinmanager/skills/)
Architecture
Clean Architecture / Ports and Adapters with strict dependency direction:
DoujinManager.Server Composition root, ASP.NET host, Scalar UI
DoujinManager.RestAdapter Primary adapter: REST endpoints, DTOs, hypermedia mapping
DoujinManager.ApplicationCore Domain model, use cases, ports/interfaces, business rules
DoujinManager.Infrastructure EF Core SQLite, filesystem storage, SkiaSharp, ZIP, search
Dependency rule: ApplicationCore depends on nothing. RestAdapter and Infrastructure depend on ApplicationCore. Server depends on all three. The REST adapter may only call use cases — never DbContext, repositories, or filesystem directly.
See Architecture Overview for the full design.
Domain model
Doujin logical work — titles, associations, rating, reading direction, cover, variants
Variant readable edition/language/version — mandatory language code, pages, optional chapters
Chapter ordered grouping inside a variant
Page stable logical page — belongs to a variant (optionally a chapter), points to an ImageFile
ImageFile physical stored image — UUID-based sharded path, hash/dimensions/media type
Tag metadata entity — name, description, aliases
Person metadata entity — display name, description, aliases; role lives on the doujin link
Circle metadata entity — display name, description, aliases
Character metadata entity — display name, description, aliases
Series metadata entity — display name, description, aliases
All five metadata types are first-class categories with the same harmonized shape (ADR 0022) and are usable in search and library filtering.
Key rules: a doujin requires at least one title. Variant language is mandatory (none and unknown are valid). Page order lives in the database, not filenames. Replacing a page image changes the referenced image file, not the page ID. The doujin model stores work data only — client/user preferences stay client-side (ADR 0023).
Technology
| Concern | Choice |
|---|---|
| Backend | .NET 10 / modern C# |
| Persistence | SQLite via EF Core |
| Image processing | SkiaSharp |
| API docs | OpenAPI + Scalar |
| Auth | Static bearer token |
| IDs | UUID (strongly typed value objects) |
| Desktop app | Flutter (Redux, dio, freezed, go_router) |
| Deployment | Docker (Synology NAS / Portainer) |
| CI/CD | Forgejo Actions |
Repository structure
backend/
src/
DoujinManager.Server/ ASP.NET host, composition root
DoujinManager.RestAdapter/ REST endpoints, DTOs, middleware
DoujinManager.ApplicationCore/ Domain entities, use cases, ports
DoujinManager.Infrastructure/ EF Core, filesystem, SkiaSharp, ZIP
tests/
DoujinManager.ApplicationCore.Tests/
DoujinManager.RestAdapter.Tests/
DoujinManager.Infrastructure.Tests/
DoujinManager.IntegrationTests/
app/
lib/
domain/ Repository ports, entities (ADR 0018)
data/ dio API client, freezed models, repo impls
presentation/ Redux state, epics, feature-first pages
test/
deploy/
Dockerfile, docker-compose.yml, DEPLOYMENT.md
docs/
ARCHITECTURE.md, PROJECT_PLAN.md, adr/
API overview
All endpoints under /api require Authorization: Bearer *** GET /health` is anonymous.
| Resource | Endpoints |
|---|---|
| Doujins | GET/POST /api/doujins, GET/PUT/DELETE /api/doujins/{id} |
| Titles | POST /api/doujins/{id}/titles, PUT/DELETE /api/doujins/{id}/titles/{titleId} |
| Associations | POST/DELETE /api/doujins/{id}/tags, .../people (role-aware), .../circles, .../characters, .../series |
| Variants | GET/POST /api/doujins/{id}/variants, GET/PUT/DELETE /api/variants/{id} |
| Chapters | GET/POST /api/variants/{id}/chapters, PUT/DELETE /api/variants/{id}/chapters/{chapterId} |
| Pages | POST /api/variants/{id}/pages[/zip] (?chapterId= optional), PUT /api/variants/{id}/pages/reorder, PUT /api/pages/{id}/chapter, DELETE /api/pages/{id} |
| Images | GET /api/images/{id}, GET /api/thumbnails/{id} |
| Metadata | GET/POST + GET/PUT/DELETE /{id} + alias endpoints for /api/tags, /api/people, /api/circles, /api/characters, /api/series |
| Search | POST /api/doujins/search (structured include/exclude query) |
| Backups | GET/POST /api/backups, DELETE /api/backups/{filename} |
Responses use a hypermedia envelope:
{
"data": {},
"links": { "self": "/api/doujins/abc-123" },
"actions": { "delete": { "method": "DELETE", "href": "/api/doujins/abc-123" } }
}
Collections include pagination:
{
"data": [],
"page": { "page": 1, "pageSize": 50, "totalItems": 123, "totalPages": 3 },
"links": {}, "actions": {}
}
Interactive API docs are available at /scalar when the server is running.
Building and running locally
Prerequisites
- .NET 10 SDK
- SQLite (included via EF Core bundle)
Run
cd backend
dotnet restore
export DOUJIN_MANAGER_AUTH_TOKEN="dev-token-change-me"
dotnet run --project src/DoujinManager.Server
The server starts on http://localhost:8080. EF Core migrations apply automatically on startup.
Run tests
cd backend
dotnet test
Flutter app
cd app
flutter pub get
dart run build_runner build --delete-conflicting-outputs
flutter run -d linux # or windows / macos
flutter test
On first launch, configure the server URL and bearer token on the Settings page.
Optional app environment variables:
| Variable | Description | Default |
|---|---|---|
DOUJIN_MANAGER_AGENT_MAX_TOOL_ROUNDS |
Tool-round cap per AI-assistant turn (runaway guard) | 64 |
DOUJIN_MANAGER_AGENT_HISTORY_BUDGET_CHARS |
Character budget for the assistant's replayed conversation history; past it, old tool results are trimmed to placeholders and then whole oldest turns are dropped (never the newest one) | 200000 |
DOUJIN_MANAGER_AGENT_REFLECTION |
Set to off to disable the assistant's self-improvement pass (memories/skills) |
on |
DOUJIN_MANAGER_AGENT_REFLECTION_DELAY |
Idle seconds after a run before the self-improvement pass starts (a new message cancels it) | 180 |
PUPPETEER_EXECUTABLE_PATH |
Browser executable for the assistant's web_browser tool (also settable in Settings) |
— |
Docker deployment
The container image is built and pushed to the Forgejo container registry on every push to main. Deploy via Portainer or docker-compose.
See the Deployment Guide for full instructions including volume setup, environment variables, backup/restore, and troubleshooting.
Quick start:
docker volume create --driver local --opt type=none --opt o=bind \
--opt device=/your/storage/path/images doujinshi_images
(Mounting a CIFS/SMB share instead? See the deployment guide §4.1 — ownership must be pinned in the mount options.)
docker run -d \
-e DOUJIN_MANAGER_AUTH_TOKEN=$(openssl rand -hex 32) \
-p 8080:8080 \
git.kagaku.eu/teamai/doujin-manager:latest
Environment variables
| Variable | Description | Default |
|---|---|---|
DOUJIN_MANAGER_AUTH_TOKEN |
Bearer token for /api/* (required) |
— |
DOUJIN_MANAGER_DB_PATH |
SQLite database file path | /app/data/db/doujin-manager.db |
DOUJIN_MANAGER_IMAGE_DIR |
Uploaded images directory | /app/data/images |
DOUJIN_MANAGER_THUMBNAIL_DIR |
Generated thumbnails directory | /app/data/thumbnails |
DOUJIN_MANAGER_BACKUP_DIR |
Database backup directory | /app/data/backups |
Documentation
- Project Plan — full scope, domain rules, MVP/non-MVP, phases
- Architecture Overview — Clean Architecture, dependency direction, request flow
- Decision Records — 23 ADRs covering tech choices, auth, domain model, storage, and implementation decisions
- Deployment Guide — Docker, volumes, backup/restore, troubleshooting
CI/CD
Two Forgejo Actions workflows:
- CI (
ci.yml) — builds and runs all tests on every push/PR tomain - Docker publish (
docker-publish.yml) — builds and pushes the container image to the Forgejo registry on every push tomain