Jomics is a self-contained comic reader web server written in Go. It serves CBZ and CBR comic archives via a browser UI with no external dependencies beyond libc and the kernel. Everything (templates, static assets, CSS) is embedded into the binary at build time via go:embed.
This is a single-file project — all Go code lives in main.go (~665 lines).
go build # produces ./jomics binary
./jomics -root /path/to/comics/collection # required flag, serves on localhost:4531Important: go-unarr (used for CBZ/CBR decompression) requires CGO and libc. You cannot build a fully static binary.
| Flag | Default | Description |
|---|---|---|
-root |
(required) | Root directory of comic collection |
-addr |
localhost:4531 |
Server address. Use :4531 to bind all interfaces |
-webroot |
(empty) | Path prefix for reverse proxy setups |
-si |
300 |
Rescan interval in seconds (0/negative disables) |
-th |
400 |
Front cover thumbnail height (100-2000) |
-light |
false |
Use light theme (default is dark) |
-q |
false |
Quiet mode |
- On startup,
scanCollection()walks the root directory recursively, discovering.cbz,.cbrfiles and subdirectories - Each file/directory is assigned a CRC32 hash (IEEE table) of its full path — this hash is the album identifier used in all URLs
prepareAlbums()extracts the first image from each archive, resizes it to the thumbnail height, and caches it to the XDG cache directory- A background goroutine periodically re-scans the collection (default every 5 minutes)
- All HTTP handler methods hold
colMutexduring archive reads — collection swap on rescan is atomic
/albums/?folder=0x{hash}— list comics in a folder/covers/?album=0x{hash}— serve front cover thumbnail/read/?album=0x{hash}&folder=0x{hash}&page=N— render page view/images/?album=0x{hash}&folder=0x{hash}&page=N— serve raw page image
Album and folder identifiers are hex-encoded CRC32 hashes passed as 0x prefixed hex strings in query parameters.
Three go:embed directives bundle assets:
tmpl/— HTML templates (frontcover.html,page.html)static/— PNG assets (favicon, folder icon, logos)css/pico-master.zip— Pico CSS framework, served at runtime viazipfs(fromgerace.dev/zipfs)
Thumbnail cache lives in the XDG cache directory (~/.cache/jomics/ on Linux, per the gmelchett vendor name). Cache filenames are {crc32hex}-{thumbHeight}. Changing -th invalidates existing thumbnails.
If a CBZ/CBR archive contains ComicInfo.xml, its <Series>, <Title>, <Number>, and <Year> fields are used to construct the display title (e.g., "Series Title Number (Year)"). Otherwise, the filename (underscores replaced with spaces, title-cased) is used.
- Single package
main— no internal packages - No tests exist
- No linter or formatter configuration
- Go 1.17 module (check
go.modbefore using newer Go features) - Struct types for HTTP handler data (
Page,Albums,FrontCover) - Method receivers on
jomicsstruct for HTTP handlers; oncomicCollectionfor collection operations - All HTTP routing via
http.HandleFunc/http.Handleon the default mux - Navigation between pages uses HTML form POSTs (not links) with hardcoded form actions
strings.Titleis deprecated (used atmain.go:246). A gopls hint flags this. Replacement usesgolang.org/x/text/cases.- CGO dependency:
go-unarrwraps libunarr (C library). Cross-compilation requires the C toolchain for the target platform. - No Content-Type for cover images:
handleFrontImagesetsapplication/octet-streamrather thanimage/jpeg/image/png. This works but is not ideal. WriteHeaderbeforeHeader().Set()inhandleFrontImage(main.go:485-486) — headers set afterWriteHeaderare ignored by the HTTP spec. This is a latent bug.- Thread safety: The
colMutexprotects the collection pointer during reads, but the periodic rescan creates a brand newcomicCollectionand swaps it atomically under the lock. - Folder as root:
main()passesfilepath.Dir(*root)asrootDir, butscanCollectionwalks*rootdirectly. The directory containing the comics directory itself is the root of the walk. rootDirusesfilepath.Dir: This means the initial directory listing shows contents one level up from-root. This may be intentional for the navigation model.