dev: public ready
dev: Readme, COC, others dev: xml-template
This commit is contained in:
parent
5db584830b
commit
1cf5119f46
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