dev: public ready #1

Merged
riomoo merged 1 commit from dev/finished into main 2025-11-19 14:33:38 -05:00
12 changed files with 3716 additions and 0 deletions
Showing only changes of commit 1cf5119f46 - Show all commits

11
.containerignore Normal file
View 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
View file

@ -0,0 +1 @@
By interacting with this repository you agree that any emotional damage is your own damn fault.

32
Containerfile Normal file
View 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
View 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
View 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
[![Custom badge](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fshare.jester-designs.com%2Fview%2Fpil.json)](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
[![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white)](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
![Arch](https://img.shields.io/badge/Arch%20Linux-1793D1?logo=arch-linux&logoColor=fff&style=for-the-badge)
![Gimp Gnu Image Manipulation Program](https://img.shields.io/badge/Gimp-657D8B?style=for-the-badge&logo=gimp&logoColor=FFFFFF)
![Vim](https://img.shields.io/badge/VIM-%2311AB00.svg?style=for-the-badge&logo=vim&logoColor=white)
![Git](https://img.shields.io/badge/git-%23F05033.svg?style=for-the-badge&logo=git&logoColor=white)
![Forgejo](https://img.shields.io/badge/forgejo-%23FB923C.svg?style=for-the-badge&logo=forgejo&logoColor=white)
</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

File diff suppressed because it is too large Load diff

View 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"

View 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
View 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
View 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
View 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=

View 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>