Merge pull request 'dev: public ready' (#1) from dev/finished into main
Reviewed-on: #1
This commit is contained in:
commit
169214b8ff
12 changed files with 3716 additions and 0 deletions
11
.containerignore
Normal file
11
.containerignore
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
.containerignore
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
bash-scripts
|
||||
Containerfile
|
||||
README.md
|
||||
xml-template
|
||||
cache
|
||||
etc
|
||||
library
|
||||
1
CODE_OF_CONDUCT.md
Normal file
1
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
By interacting with this repository you agree that any emotional damage is your own damn fault.
|
||||
32
Containerfile
Normal file
32
Containerfile
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Build stage
|
||||
FROM golang:bookworm AS builder
|
||||
|
||||
# Install UPX
|
||||
RUN apt-get update && apt-get install -y wget xz-utils && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN wget https://github.com/upx/upx/releases/download/v5.0.2/upx-5.0.2-amd64_linux.tar.xz
|
||||
RUN tar -xf upx-5.0.2-amd64_linux.tar.xz && mv upx-5.0.2-amd64_linux/upx /usr/local/bin/upx && rm -r upx-5.0.2-amd64_linux upx-5.0.2-amd64_linux.tar.xz
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN mkdir -p /var/sockets
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags="-s -w -extldflags '-static' -X main.GOMEMLIMIT=256MiB -X runtime.defaultGOGC=50" -trimpath -gcflags="-l=4" -asmflags=-trimpath -o bin/main app/gopherbook/main.go
|
||||
RUN upx --best --ultra-brute bin/main
|
||||
RUN chmod +x bin/main
|
||||
|
||||
# Final stage with Chainguard static
|
||||
FROM cgr.dev/chainguard/static:latest
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the binary
|
||||
COPY --from=builder /app/bin/main ./bin/main
|
||||
|
||||
# Create directories that will be mounted and set ownership
|
||||
EXPOSE 8080
|
||||
USER root:root
|
||||
CMD ["./bin/main"]
|
||||
17
LICENSE
Normal file
17
LICENSE
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
Copyright (c) 2025 Riomoo [alister at kamikishi dot net]
|
||||
|
||||
This software is licensed under the Prism Information License (PIL).
|
||||
|
||||
- You are free to use, modify, and distribute this software.
|
||||
- Any derivative work must include this license.
|
||||
- You must also provide a concise explanation of how to operate this software,
|
||||
as well as backends and frontends (if any) that work with this software.
|
||||
- You must credit the original creator of this software.
|
||||
- You may choose to license this software under additional licenses, provided that the terms of the original PIL are still adhered to.
|
||||
|
||||
This software is provided "as is", without any warranty of any kind,
|
||||
express or implied, including but not limited to the warranties of merchantability,
|
||||
fitness for a particular purpose, and non-infringement.
|
||||
|
||||
By using this software, you agree to the terms of the PIL.
|
||||
For more information visit: https://pil.jester-designs.com/
|
||||
170
README.md
Normal file
170
README.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
# Gopherbook – Self-Hosted Comic Library & CBZ Reader
|
||||
|
||||
Gopherbook is a lightweight, single-binary, self-hosted web comic reader and library manager written in Go.
|
||||
It is designed for people who want full control over their digital comic collection (CBZ files), including support for password-protected/encrypted archives, per-user libraries, tagging, automatic organization, and a clean modern reader.
|
||||
|
||||
## License
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
## Features
|
||||
|
||||
- Upload & read `.cbz` (ZIP-based) comics directly in the browser
|
||||
- Full support for password-protected/encrypted CBZ files (AES-256 via yeka/zip)
|
||||
- Automatically tries all previously successful passwords when opening a new encrypted comic
|
||||
- Persists discovered passwords securely (AES-encrypted on disk, key derived from your login password)
|
||||
- Extracts ComicInfo.xml metadata (title, series, number, writer, inker, tags, story arc, etc.)
|
||||
- Automatic folder organization: `Library/Artist/StoryArc/Comic.cbz`
|
||||
- Manual reorganization via UI if needed
|
||||
- Powerful tagging system with custom colors and counts
|
||||
- Filter comics by any combination of tags
|
||||
- Responsive grid view with cached JPEG covers
|
||||
- Full-screen web reader with:
|
||||
- Page pre-loading
|
||||
- Zoom / pan
|
||||
- Fit-to-width/height/page
|
||||
- Keyboard navigation (←→, A/D, +/-, Esc)
|
||||
- Multi-user support:
|
||||
- Each user has their own completely isolated library and password vault
|
||||
- First registered user becomes admin
|
||||
- Admin can disable new registrations
|
||||
- Admin can delete any comic
|
||||
- No external database – everything stored in simple JSON files
|
||||
- Single static Go binary + file storage – easy to deploy
|
||||
|
||||
## Installation / Running
|
||||
|
||||
### Prerequisites
|
||||
[](https://golang.org/dl/)
|
||||
- Go 1.25.2+ (only needed to build)
|
||||
- Or just download a pre-built binary from Releases (when available)
|
||||
|
||||
### Quick start (from source)
|
||||
|
||||
```bash
|
||||
git clone https://codeberg.org/riomoo/gopherbook.git
|
||||
cd gopherbook
|
||||
go build -o gopherbook app/gopherbook/main.go
|
||||
./gopherbook
|
||||
```
|
||||
|
||||
Then open http://localhost:8080 in your browser.
|
||||
|
||||
## If you want to use this with podman:
|
||||
|
||||
```bash
|
||||
git clone https://codeberg.org/riomoo/gopherbook.git
|
||||
cd gopherbook
|
||||
./bash-scripts/run.sh
|
||||
```
|
||||
|
||||
Then open http://localhost:12010 in your browser.
|
||||
|
||||
### First launch
|
||||
1. On first run there are no users → registration is open
|
||||
2. Create the first account → this user automatically becomes admin
|
||||
3. Log in → start uploading CBZ files
|
||||
|
||||
## Directory layout after first login
|
||||
|
||||
```
|
||||
./library/username/ ← your comics (organized or Unorganized/)
|
||||
./library/username/comics.json ← metadata index
|
||||
./library/username/tags.json ← tag definitions & counts
|
||||
./library/username/passwords.json ← encrypted password vault (AES)
|
||||
./cache/covers/username/ ← generated cover thumbnails
|
||||
./etc/users.json ← user accounts (bcrypt hashes)
|
||||
./etc/admin.json ← admin settings (registration toggle)
|
||||
```
|
||||
|
||||
## How encrypted/password-protected comics work
|
||||
|
||||
- When you upload or scan an encrypted CBZ that has no known password yet, the server marks it as Encrypted = true.
|
||||
- The first time you open it in the reader, a password prompt appears.
|
||||
- If the password is correct, Gopherbook:
|
||||
- Stores the password (encrypted with a key derived from your login password)
|
||||
- Extracts ComicInfo.xml metadata
|
||||
- Auto-organizes the file into Artist/StoryArc folders
|
||||
- Updates tags and cover cache
|
||||
- From then on the comic opens instantly, and that password is automatically tried on every future encrypted comic you upload (so whole collections that share one password "just work").
|
||||
|
||||
## Security notes
|
||||
|
||||
- Passwords are stored encrypted on disk using AES-256-CFB with a key derived from your login password via SHA-256.
|
||||
- Session cookie is HttpOnly, expires after 24 h.
|
||||
- No external dependencies that phone home.
|
||||
- Still: treat this as a personal/private server – do not expose it publicly without HTTPS/reverse-proxy auth.
|
||||
|
||||
## Config for NGINX to use as a website:
|
||||
```
|
||||
upstream gopherbook {
|
||||
server 127.0.0.1:8080;
|
||||
#server 127.0.0.1:12010; #For Podman instead
|
||||
server [::1]:8080;
|
||||
#server [::1]:12010; #For Podman instead
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
listen [::1]:80;
|
||||
server_name gopherbook.example.com;
|
||||
location / {
|
||||
proxy_pass http://gopherbook;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# 1. Allow very large uploads (e.g. 500 MB – adjust as needed)
|
||||
client_max_body_size 500M; # 0 = unlimited, but never do that on a public server
|
||||
|
||||
# 2. Give the upload enough time (important for slow connections)
|
||||
client_body_timeout 5m; # time to read the entire request body (default 60s)
|
||||
proxy_read_timeout 5m; # if you're proxying to your Go app
|
||||
proxy_send_timeout 5m;
|
||||
|
||||
# 3. Increase buffer sizes so Nginx doesn't spill everything to disk
|
||||
client_body_buffer_size 512k; # default 8k/16k – too small for big uploads
|
||||
proxy_buffers 8 512k;
|
||||
proxy_buffer_size 256k;
|
||||
|
||||
# 4. Recommended: put uploads in a temporary directory with plenty of space
|
||||
client_body_temp_path /var/lib/nginx/body 1 2; # make sure this directory exists and is writable by nginx
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome! Especially:
|
||||
- Better mobile reader experience
|
||||
- Bulk tag editing
|
||||
- Search box
|
||||
- OPDS catalog endpoint
|
||||
- More metadata sources (ComicVine, etc.)
|
||||
|
||||
Please open an issue first for bigger changes.
|
||||
|
||||
## Thanks / Credits
|
||||
|
||||
- yeka/zip – password-protected ZIP support in pure Go
|
||||
- The ComicRack ComicInfo.xml standard
|
||||
- Everyone who hoards comics ❤️
|
||||
|
||||
Enjoy your library!
|
||||
— Happy reading with Gopherbook
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Software Used but not included
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## Creator:
|
||||
- [Codeberg Riomoo](https://codeberg.org/riomoo)
|
||||
- [GitGud Riomoo](https://gitgud.io/riomoo)
|
||||
- [Github Riomoo](https://github.com/riomoo)
|
||||
3328
app/gopherbook/main.go
Normal file
3328
app/gopherbook/main.go
Normal file
File diff suppressed because it is too large
Load diff
70
bash-scripts/comic-page-number.sh
Executable file
70
bash-scripts/comic-page-number.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
|
||||
# --- Configuration ---
|
||||
# Set the directory containing your comic book images
|
||||
IMAGE_DIR="./"
|
||||
|
||||
# Set the name of the file to save the output to
|
||||
OUTPUT_FILE="pages_xml_output.txt"
|
||||
|
||||
# --- Script Start ---
|
||||
|
||||
# Check if the image directory exists
|
||||
if [ ! -d "$IMAGE_DIR" ]; then
|
||||
echo "Error: Directory '$IMAGE_DIR' not found." >&2
|
||||
echo "Please create the directory and place your images inside, or update the IMAGE_DIR variable." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure the output file is clear or create it
|
||||
> "$OUTPUT_FILE"
|
||||
|
||||
# Start the <Pages> tag
|
||||
echo " <Pages>" >> "$OUTPUT_FILE"
|
||||
|
||||
# Initialize a counter for the <Page Image="..."> attribute
|
||||
page_counter=0
|
||||
|
||||
# Use 'find' to get a list of image files, sort them numerically (important for comic order)
|
||||
# Adjust the pattern (*.jpg|*.jpeg|*.png) to match your file types if necessary
|
||||
find "$IMAGE_DIR" -maxdepth 1 -type f -regex ".*\.\(jpg\|jpeg\|png\|jxl\|avif\)$" | sort -V | while read -r image_path; do
|
||||
|
||||
# 1. Get the ImageSize in bytes
|
||||
# Use 'stat' with a format to get the size in bytes (the command might differ slightly on non-Linux systems like macOS)
|
||||
# Linux (GNU stat): %s
|
||||
# macOS (BSD stat): %z
|
||||
|
||||
# Simple cross-platform attempt (often works):
|
||||
# FALLBACK: If the 'stat' command is complex or fails, a simple 'wc -c' (byte count) can be used.
|
||||
# We will try 'stat' for better compatibility with typical setups.
|
||||
|
||||
file_size_bytes=$(stat -c%s "$image_path" 2>/dev/null || wc -c < "$image_path")
|
||||
|
||||
# 2. Determine if it's the first page (for Type="FrontCover")
|
||||
if [ "$page_counter" -eq 0 ]; then
|
||||
# This is the cover page (Page Image="0")
|
||||
xml_line=" <Page Image=\"${page_counter}\" ImageSize=\"${file_size_bytes}\" Type=\"FrontCover\"/>"
|
||||
else
|
||||
# Subsequent pages
|
||||
xml_line=" <Page Image=\"${page_counter}\" ImageSize=\"${file_size_bytes}\"/>"
|
||||
fi
|
||||
|
||||
# 3. Print the generated XML line
|
||||
echo "$xml_line" >> "$OUTPUT_FILE"
|
||||
|
||||
# 4. Increment the counter
|
||||
((page_counter++))
|
||||
|
||||
done
|
||||
|
||||
# End the <Pages> tag
|
||||
echo " </Pages>" >> "$OUTPUT_FILE"
|
||||
|
||||
echo "---"
|
||||
echo "✅ Script completed."
|
||||
echo "Generated $page_counter <Page> entries and saved the output to: $OUTPUT_FILE"
|
||||
echo "You can now paste the content of '$OUTPUT_FILE' into your ComicInfo.xml file."
|
||||
echo "---"
|
||||
|
||||
# Optional: Display the content of the generated file
|
||||
cat "$OUTPUT_FILE"
|
||||
5
bash-scripts/make-cbz-imagemagick.sh
Executable file
5
bash-scripts/make-cbz-imagemagick.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
user="$(gpg2 -qd /home/moo/test/gopherbook/moo.pass.asc)"
|
||||
EXTEN="jpg"
|
||||
mkdir -p ./cbzs
|
||||
7z a -tzip -mem=AES256 -mx=9 ./cbzs/$1-others.cbz -p *.$EXTEN ComicInfo.xml
|
||||
35
bash-scripts/run.sh
Executable file
35
bash-scripts/run.sh
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
IMAGE_NAME="localhost/gopherbook:latest"
|
||||
CONTAINER_NAME="gopherbook"
|
||||
|
||||
echo "Building new image: $IMAGE_NAME..."
|
||||
podman build --force-rm -t "$IMAGE_NAME" .
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Image build failed. Exiting script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure directories exist with correct permissions
|
||||
mkdir -p ./library ./cache ./etc
|
||||
|
||||
if podman container exists "$CONTAINER_NAME"; then
|
||||
echo "Container '$CONTAINER_NAME' already exists. Stopping and removing it..."
|
||||
podman stop "$CONTAINER_NAME"
|
||||
podman rm "$CONTAINER_NAME"
|
||||
fi
|
||||
|
||||
echo "Starting new container from image: $IMAGE_NAME..."
|
||||
podman run -d --name "$CONTAINER_NAME" --memory=256m --restart unless-stopped \
|
||||
-p 12010:8080 -v ./library:/app/library -v ./cache:/app/cache -v ./etc:/app/etc "$IMAGE_NAME"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to start new container. Exiting script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Cleaning up old images..."
|
||||
podman image prune --force
|
||||
|
||||
echo "Update and cleanup complete!"
|
||||
8
go.mod
Normal file
8
go.mod
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module gobook
|
||||
|
||||
go 1.25.2
|
||||
|
||||
require (
|
||||
github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9
|
||||
golang.org/x/crypto v0.43.0
|
||||
)
|
||||
4
go.sum
Normal file
4
go.sum
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9 h1:K8gF0eekWPEX+57l30ixxzGhHH/qscI3JCnuhbN6V4M=
|
||||
github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9/go.mod h1:9BnoKCcgJ/+SLhfAXj15352hTOuVmG5Gzo8xNRINfqI=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
35
xml-template/ComicInfo.xml
Normal file
35
xml-template/ComicInfo.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ComicInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://anansi-project.github.io/docs/comicinfo/schemas/v2.1">
|
||||
<AgeRating>Adults Only 18+</AgeRating>
|
||||
<BlackAndWhite>No</BlackAndWhite>
|
||||
<Format>CBZ</Format>
|
||||
<Inker>Artist Name</Inker>
|
||||
<Writer>Artist Name</Writer>
|
||||
<Penciller>Artist Name</Penciller>
|
||||
<CoverArtist>Artist Name</CoverArtist>
|
||||
<Publisher>Publisher Name</Publisher>
|
||||
<LanguageISO>en</LanguageISO>
|
||||
<Manga>No</Manga>
|
||||
<Notes>CBZ created by nobodyatall@cock.li</Notes>
|
||||
<Tags>Blood, Gore, Comedy, Dark Comedy, Mature, Leading Ladies, Fantasy</Tags>
|
||||
<ScanInformation>person who scanned comic</ScanInformation>
|
||||
<PageCount>5</PageCount>
|
||||
<Pages>
|
||||
<Page Image="0" ImageSize="1702703" Type="FrontCover"/>
|
||||
<Page Image="1" ImageSize="332602"/>
|
||||
<Page Image="2" ImageSize="3535719"/>
|
||||
<Page Image="3" ImageSize="3424043"/>
|
||||
<Page Image="4" ImageSize="3063517"/>
|
||||
</Pages>
|
||||
<Series>Main Title</Series>
|
||||
<StoryArc>Sub Title</StoryArc>
|
||||
<StoryArcNumber>1</StoryArcNumber>
|
||||
<Summary>Summary of book</Summary>
|
||||
<Title>Main Title: Sub Title</Title>
|
||||
<Year>2017</Year>
|
||||
<Month>9</Month>
|
||||
<Day>11</Day>
|
||||
<Number>1</Number>
|
||||
<Count>1</Count>
|
||||
<Volume>1</Volume>
|
||||
</ComicInfo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue