Compare commits
3 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0efc83cf6 | |||
| 05a1723805 | |||
| bd8437bd1f |
18 changed files with 456 additions and 47 deletions
|
|
@ -2,11 +2,15 @@
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
.gitattributes
|
.gitattributes
|
||||||
bash-scripts
|
scripts-bash
|
||||||
|
scripts-go
|
||||||
Containerfile
|
Containerfile
|
||||||
|
Containerfile.build
|
||||||
|
binaries
|
||||||
README.md
|
README.md
|
||||||
xml-template
|
xml-template
|
||||||
cache
|
cache
|
||||||
etc
|
etc
|
||||||
library
|
library
|
||||||
watch
|
watch
|
||||||
|
releases
|
||||||
|
|
|
||||||
7
.gitattributes
vendored
7
.gitattributes
vendored
|
|
@ -1,8 +1,5 @@
|
||||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
||||||
*.lua filter=lfs diff=lfs merge=lfs -text
|
*.lua filter=lfs diff=lfs merge=lfs -text
|
||||||
*.svg filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.webp filter=lfs diff=lfs merge=lfs -text
|
*.webp filter=lfs diff=lfs merge=lfs -text
|
||||||
*.gif filter=lfs diff=lfs merge=lfs -text
|
*.gif filter=lfs diff=lfs merge=lfs -text
|
||||||
*.webm filter=lfs diff=lfs merge=lfs -text
|
*.webm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
@ -31,7 +28,3 @@
|
||||||
*.odt filter=lfs diff=lfs merge=lfs -text
|
*.odt filter=lfs diff=lfs merge=lfs -text
|
||||||
*.docx filter=lfs diff=lfs merge=lfs -text
|
*.docx filter=lfs diff=lfs merge=lfs -text
|
||||||
*.apk filter=lfs diff=lfs merge=lfs -text
|
*.apk filter=lfs diff=lfs merge=lfs -text
|
||||||
*.ico filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.JXL filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.AVIF filter=lfs diff=lfs merge=lfs -text
|
|
||||||
*.PNG filter=lfs diff=lfs merge=lfs -text
|
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -7,3 +7,5 @@ cache
|
||||||
library
|
library
|
||||||
etc
|
etc
|
||||||
watch
|
watch
|
||||||
|
binaries
|
||||||
|
releases
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build \
|
||||||
-a \
|
-a \
|
||||||
-ldflags="-s -w -linkmode external -extldflags '-static' -X main.GOMEMLIMIT=512MiB -X runtime.defaultGOGC=50" \
|
-ldflags="-s -w -linkmode external -extldflags '-static' -X main.GOMEMLIMIT=512MiB -X runtime.defaultGOGC=50" \
|
||||||
-trimpath \
|
-trimpath \
|
||||||
-o bin/main app/gopherbook/main.go
|
-o bin/main ./app/gopherbook
|
||||||
RUN upx --best --ultra-brute bin/main
|
RUN upx --best --ultra-brute bin/main
|
||||||
RUN chmod +x bin/main
|
RUN chmod +x bin/main
|
||||||
|
|
||||||
|
|
|
||||||
55
Containerfile.build
Normal file
55
Containerfile.build
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Multi-platform build container for Gopherbook
|
||||||
|
FROM golang:alpine AS builder
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
musl-dev \
|
||||||
|
gcc \
|
||||||
|
g++ \
|
||||||
|
mingw-w64-gcc \
|
||||||
|
wget \
|
||||||
|
xz \
|
||||||
|
git
|
||||||
|
|
||||||
|
# Install UPX for binary compression
|
||||||
|
RUN wget https://github.com/upx/upx/releases/download/v5.0.2/upx-5.0.2-amd64_linux.tar.xz && \
|
||||||
|
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 go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build Linux binary
|
||||||
|
RUN echo "Building Linux binary..." && \
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build \
|
||||||
|
-a \
|
||||||
|
-ldflags="-s -w -linkmode external -extldflags '-static'" \
|
||||||
|
-trimpath \
|
||||||
|
-o bin/gopherbook-linux ./app/gopherbook && \
|
||||||
|
upx --best --ultra-brute bin/gopherbook-linux && \
|
||||||
|
chmod +x bin/gopherbook-linux
|
||||||
|
|
||||||
|
# Build Windows binary
|
||||||
|
RUN echo "Building Windows binary..." && \
|
||||||
|
echo 'IDI_ICON1 ICON "./app/gopherbook/static/images/favicon/favicon.ico"' > gopherbook.rc && \
|
||||||
|
x86_64-w64-mingw32-windres gopherbook.rc -o ./app/gopherbook/gopherbook.syso && \
|
||||||
|
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc go build \
|
||||||
|
-a \
|
||||||
|
-ldflags="-s -w" \
|
||||||
|
-trimpath \
|
||||||
|
-o bin/gopherbook-windows.exe ./app/gopherbook && \
|
||||||
|
upx --best --ultra-brute bin/gopherbook-windows.exe && \
|
||||||
|
rm ./app/gopherbook/gopherbook.syso gopherbook.rc
|
||||||
|
|
||||||
|
# Verify binaries were created
|
||||||
|
RUN ls -lh bin/ && \
|
||||||
|
echo "Build complete!" && \
|
||||||
|
echo "Linux binary size: $(du -h bin/gopherbook-linux | cut -f1)" && \
|
||||||
|
echo "Windows binary size: $(du -h bin/gopherbook-windows.exe | cut -f1)"
|
||||||
|
|
||||||
|
# Keep the builder stage as the final stage so we can copy files out
|
||||||
|
FROM builder
|
||||||
4
Makefile
4
Makefile
|
|
@ -1,5 +1,5 @@
|
||||||
build:
|
build:
|
||||||
go build -o bin/main app/gopherbook/main.go
|
go build -o bin/main ./app/gopherbook
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf watch etc library cache
|
rm -rf watch etc library cache binaries releases
|
||||||
|
|
|
||||||
27
README.md
27
README.md
|
|
@ -1,11 +1,21 @@
|
||||||
# Gopherbook – Self-Hosted Comic Library & CBZ/CBT Reader
|
<div align="center">
|
||||||
|
|
||||||
|
<img src="https://git.jester-designs.com/riomoo/gopherbook/media/branch/main/docs/images/gopherbook-title.png" alt="Description" width="50%">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
## Self-Hosted Comic Library & CBZ/CBT Reader
|
||||||
|
|
||||||
Gopherbook is a lightweight, single-binary, self-hosted web comic reader and library manager written in Go.
|
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/CBT files), including support for password-protected/encrypted archives, per-user libraries, tagging, automatic organization, and a clean modern reader.
|
It is designed for people who want full control over their digital comic collection (CBZ/CBT files), including support for password-protected/encrypted archives, per-user libraries, tagging, automatic organization, and a clean modern reader.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
@ -78,6 +88,15 @@ Then open http://localhost:12010 in your browser.
|
||||||
./etc/admin.json ← admin settings (registration toggle)
|
./etc/admin.json ← admin settings (registration toggle)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
Can be used to set where everything is stored with:
|
||||||
|
```
|
||||||
|
GOPHERBOOK_LIBRARY=$HOME/.config/gopherbook/library
|
||||||
|
GOPHERBOOK_CACHE=$HOME/.config/gopherbook/covers
|
||||||
|
GOPHERBOOK_ETC=$HOME/.config/gopherbook/etc
|
||||||
|
GOPHERBOOK_WATCH=$HOME/.config/gopherbook/watch
|
||||||
|
```
|
||||||
|
|
||||||
## Watch folder for bulk imports
|
## Watch folder for bulk imports
|
||||||
|
|
||||||
Gopherbook includes an automatic watch folder system that makes bulk importing comics effortless:
|
Gopherbook includes an automatic watch folder system that makes bulk importing comics effortless:
|
||||||
|
|
@ -177,6 +196,10 @@ Pull requests are welcome! Especially:
|
||||||
|
|
||||||
Please open an issue first for bigger changes.
|
Please open an issue first for bigger changes.
|
||||||
|
|
||||||
|
## If you'd like to know about the new mascot Vinny
|
||||||
|
|
||||||
|
- Check the [Wiki](/riomoo/gopherbook/wiki/Meet-Vinny.md)!
|
||||||
|
|
||||||
## Thanks / Credits
|
## Thanks / Credits
|
||||||
|
|
||||||
- yeka/zip – password-protected ZIP support in pure Go
|
- yeka/zip – password-protected ZIP support in pure Go
|
||||||
|
|
|
||||||
39
app/gopherbook/config.go
Normal file
39
app/gopherbook/config.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseLibraryPath string
|
||||||
|
baseCachePath string
|
||||||
|
baseEtcPath string
|
||||||
|
baseWatchPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Store the base paths (before user-specific paths are added)
|
||||||
|
baseLibraryPath = libraryPath
|
||||||
|
baseCachePath = cachePath
|
||||||
|
baseEtcPath = etcPath
|
||||||
|
baseWatchPath = watchPath
|
||||||
|
|
||||||
|
// Override from environment variables if set
|
||||||
|
if env := os.Getenv("GOPHERBOOK_LIBRARY"); env != "" {
|
||||||
|
baseLibraryPath = filepath.Clean(env)
|
||||||
|
libraryPath = baseLibraryPath
|
||||||
|
}
|
||||||
|
if env := os.Getenv("GOPHERBOOK_CACHE"); env != "" {
|
||||||
|
baseCachePath = filepath.Clean(env)
|
||||||
|
cachePath = baseCachePath
|
||||||
|
}
|
||||||
|
if env := os.Getenv("GOPHERBOOK_ETC"); env != "" {
|
||||||
|
baseEtcPath = filepath.Clean(env)
|
||||||
|
etcPath = baseEtcPath
|
||||||
|
}
|
||||||
|
if env := os.Getenv("GOPHERBOOK_WATCH"); env != "" {
|
||||||
|
baseWatchPath = filepath.Clean(env)
|
||||||
|
watchPath = baseWatchPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"io/fs"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
@ -39,6 +40,9 @@ import (
|
||||||
//go:embed templates/index.html
|
//go:embed templates/index.html
|
||||||
var templateFS embed.FS
|
var templateFS embed.FS
|
||||||
|
|
||||||
|
//go:embed all:static
|
||||||
|
var staticFS embed.FS
|
||||||
|
|
||||||
// ComicInfo represents the standard ComicInfo.xml metadata
|
// ComicInfo represents the standard ComicInfo.xml metadata
|
||||||
type ComicInfo struct {
|
type ComicInfo struct {
|
||||||
XMLName xml.Name `xml:"ComicInfo"`
|
XMLName xml.Name `xml:"ComicInfo"`
|
||||||
|
|
@ -135,6 +139,14 @@ func main() {
|
||||||
|
|
||||||
loadUsers()
|
loadUsers()
|
||||||
initWatchFolders()
|
initWatchFolders()
|
||||||
|
// Create static sub-filesystem once
|
||||||
|
staticSubFS, err := fs.Sub(staticFS, "static")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(fmt.Errorf("failed to create static sub-filesystem: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create handlers once and reuse
|
||||||
|
staticHandler := http.FileServer(http.FS(staticSubFS))
|
||||||
|
|
||||||
http.HandleFunc("/api/register", handleRegister)
|
http.HandleFunc("/api/register", handleRegister)
|
||||||
http.HandleFunc("/api/login", handleLogin)
|
http.HandleFunc("/api/login", handleLogin)
|
||||||
|
|
@ -154,6 +166,7 @@ func main() {
|
||||||
http.HandleFunc("/api/admin/delete-comic/", authMiddleware(handleDeleteComic))
|
http.HandleFunc("/api/admin/delete-comic/", authMiddleware(handleDeleteComic))
|
||||||
http.HandleFunc("/api/watch-folder", authMiddleware(handleWatchFolder))
|
http.HandleFunc("/api/watch-folder", authMiddleware(handleWatchFolder))
|
||||||
http.HandleFunc("/", serveUI)
|
http.HandleFunc("/", serveUI)
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", staticHandler))
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
|
|
@ -544,8 +557,8 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
currentUser = req.Username
|
currentUser = req.Username
|
||||||
key := deriveKey(req.Password)
|
key := deriveKey(req.Password)
|
||||||
libraryPath = filepath.Join("./library", currentUser)
|
libraryPath = filepath.Join(baseLibraryPath, currentUser)
|
||||||
cachePath = filepath.Join("./cache/covers", currentUser)
|
cachePath = filepath.Join(baseCachePath, currentUser)
|
||||||
os.MkdirAll(filepath.Join(libraryPath, "Unorganized"), 0755)
|
os.MkdirAll(filepath.Join(libraryPath, "Unorganized"), 0755)
|
||||||
os.MkdirAll(cachePath, 0755)
|
os.MkdirAll(cachePath, 0755)
|
||||||
|
|
||||||
|
|
@ -599,8 +612,8 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
passwordsMutex.Unlock()
|
passwordsMutex.Unlock()
|
||||||
currentEncryptionKey = nil
|
currentEncryptionKey = nil
|
||||||
currentUser = ""
|
currentUser = ""
|
||||||
libraryPath = "./library"
|
libraryPath = baseLibraryPath
|
||||||
cachePath = "./cache/covers"
|
cachePath = baseCachePath
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "session",
|
Name: "session",
|
||||||
|
|
@ -1599,7 +1612,7 @@ func saveJPEG(img image.Image, path string) error {
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
|
|
||||||
// Lower quality = smaller memory footprint during encoding
|
// Lower quality = smaller memory footprint during encoding
|
||||||
err = jpeg.Encode(out, img, &jpeg.Options{Quality: 70})
|
err = jpeg.Encode(out, img, &jpeg.Options{Quality: 85})
|
||||||
img = nil
|
img = nil
|
||||||
|
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
@ -1643,7 +1656,7 @@ func handleTags(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Color == "" {
|
if req.Color == "" {
|
||||||
req.Color = "#1f6feb"
|
req.Color = "#446B6E"
|
||||||
}
|
}
|
||||||
|
|
||||||
tagsMutex.Lock()
|
tagsMutex.Lock()
|
||||||
|
|
@ -1832,7 +1845,7 @@ func handleTryKnownPasswords(w http.ResponseWriter, r *http.Request) {
|
||||||
tagData.Count++
|
tagData.Count++
|
||||||
tags[tag] = tagData
|
tags[tag] = tagData
|
||||||
} else {
|
} else {
|
||||||
tags[tag] = Tag{Name: tag, Color: "#1f6feb", Count: 1}
|
tags[tag] = Tag{Name: tag, Color: "#446B6E", Count: 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tagsMutex.Unlock()
|
tagsMutex.Unlock()
|
||||||
|
|
@ -1947,7 +1960,7 @@ func handleSetPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
tagData.Count++
|
tagData.Count++
|
||||||
tags[tag] = tagData
|
tags[tag] = tagData
|
||||||
} else {
|
} else {
|
||||||
tags[tag] = Tag{Name: tag, Color: "#1f6feb", Count: 1}
|
tags[tag] = Tag{Name: tag, Color: "#446B6E", Count: 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tagsMutex.Unlock()
|
tagsMutex.Unlock()
|
||||||
|
|
@ -2419,7 +2432,7 @@ func processComic(filePath, filename string, modTime time.Time) Comic {
|
||||||
tagData.Count++
|
tagData.Count++
|
||||||
tags[tag] = tagData
|
tags[tag] = tagData
|
||||||
} else {
|
} else {
|
||||||
tags[tag] = Tag{Name: tag, Color: "#1f6feb", Count: 1}
|
tags[tag] = Tag{Name: tag, Color: "#446B6E", Count: 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tagsMutex.Unlock()
|
tagsMutex.Unlock()
|
||||||
|
|
@ -2481,7 +2494,7 @@ func loadComicMetadataLazy(comicID string) error {
|
||||||
tagData.Count++
|
tagData.Count++
|
||||||
tags[tag] = tagData
|
tags[tag] = tagData
|
||||||
} else {
|
} else {
|
||||||
tags[tag] = Tag{Name: tag, Color: "#1f6feb", Count: 1}
|
tags[tag] = Tag{Name: tag, Color: "#446B6E", Count: 1}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tagsMutex.Unlock()
|
tagsMutex.Unlock()
|
||||||
|
|
@ -2697,9 +2710,16 @@ func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||||
next(w, r)
|
next(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func getUsersPath() string {
|
||||||
|
return filepath.Join(etcPath, "users.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAdminPath() string {
|
||||||
|
return filepath.Join(etcPath, "admin.json")
|
||||||
|
}
|
||||||
|
|
||||||
func loadUsers() {
|
func loadUsers() {
|
||||||
data, err := os.ReadFile("etc/users.json")
|
data, err := os.ReadFile(getUsersPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -2707,7 +2727,7 @@ func loadUsers() {
|
||||||
log.Printf("Error unmarshaling users: %v", err)
|
log.Printf("Error unmarshaling users: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
adminData, err := os.ReadFile("etc/admin.json")
|
adminData, err := os.ReadFile(getAdminPath())
|
||||||
if err == nil && len(adminData) > 0 {
|
if err == nil && len(adminData) > 0 {
|
||||||
var adminConfig struct{ RegistrationEnabled bool }
|
var adminConfig struct{ RegistrationEnabled bool }
|
||||||
if err := json.Unmarshal(adminData, &adminConfig); err == nil {
|
if err := json.Unmarshal(adminData, &adminConfig); err == nil {
|
||||||
|
|
@ -2718,13 +2738,13 @@ func loadUsers() {
|
||||||
|
|
||||||
func saveUsers() {
|
func saveUsers() {
|
||||||
data, _ := json.MarshalIndent(users, "", " ")
|
data, _ := json.MarshalIndent(users, "", " ")
|
||||||
os.WriteFile("etc/users.json", data, 0644)
|
os.WriteFile(getUsersPath(), data, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveAdminConfig() {
|
func saveAdminConfig() {
|
||||||
config := struct{ RegistrationEnabled bool }{RegistrationEnabled: registrationEnabled}
|
config := struct{ RegistrationEnabled bool }{RegistrationEnabled: registrationEnabled}
|
||||||
data, _ := json.MarshalIndent(config, "", " ")
|
data, _ := json.MarshalIndent(config, "", " ")
|
||||||
os.WriteFile("etc/admin.json", data, 0644)
|
os.WriteFile(getAdminPath(), data, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTags() {
|
func loadTags() {
|
||||||
|
|
|
||||||
BIN
app/gopherbook/static/images/favicon/favicon.ico
Normal file
BIN
app/gopherbook/static/images/favicon/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 433 KiB |
BIN
app/gopherbook/static/images/pngs/CutePose2.png
Normal file
BIN
app/gopherbook/static/images/pngs/CutePose2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 327 KiB |
BIN
app/gopherbook/static/images/pngs/LogoPose2.png
Normal file
BIN
app/gopherbook/static/images/pngs/LogoPose2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
|
|
@ -3,6 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="/static/images/favicon/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
||||||
<title>Gopherbook</title>
|
<title>Gopherbook</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
|
|
@ -27,7 +28,7 @@
|
||||||
header {
|
header {
|
||||||
background: #395E62;
|
background: #395E62;
|
||||||
border-bottom: 1px solid #314C52;
|
border-bottom: 1px solid #314C52;
|
||||||
padding: 20px 0;
|
padding: 10px 0;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +44,8 @@
|
||||||
h1 {
|
h1 {
|
||||||
color: #1b1e2c;
|
color: #1b1e2c;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
padding: 20px 10px;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-section {
|
.auth-section {
|
||||||
|
|
@ -243,7 +246,7 @@
|
||||||
|
|
||||||
.comic-artist {
|
.comic-artist {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #1f6feb;
|
background: #446B6E;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|
@ -275,10 +278,18 @@
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
color: #1b1e2c;
|
||||||
|
}
|
||||||
|
|
||||||
.tab.active {
|
.tab.active {
|
||||||
color: #395E62;
|
color: #395E62;
|
||||||
border-bottom-color: #395E62;
|
border-bottom-color: #395E62;
|
||||||
}
|
}
|
||||||
|
.tab.active:hover {
|
||||||
|
color: #1b1e2c;
|
||||||
|
border-bottom-color: #395E62;
|
||||||
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
@ -334,7 +345,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-indicator {
|
.bookmark-indicator {
|
||||||
color: #f0883e;
|
color: #395E62;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -345,7 +356,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
background: #f0883e;
|
background: #395E62;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -370,7 +381,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-list-title {
|
.bookmark-list-title {
|
||||||
color: #f0883e;
|
color: #395E62;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
@ -394,8 +405,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-list-item.current {
|
.bookmark-list-item.current {
|
||||||
border-color: #f0883e;
|
border-color: #395E62;
|
||||||
background: rgba(240, 136, 62, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-delete {
|
.bookmark-delete {
|
||||||
|
|
@ -539,7 +549,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-title {
|
.modal-title {
|
||||||
color: #58a6ff;
|
color: #446B6E;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
@ -667,7 +677,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-modal-title {
|
.password-modal-title {
|
||||||
color: #58a6ff;
|
color: #446B6E;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
@ -701,7 +711,7 @@
|
||||||
|
|
||||||
.password-input-group input:focus {
|
.password-input-group input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #58a6ff;
|
border-color: #446B6E;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-modal-buttons {
|
.password-modal-buttons {
|
||||||
|
|
@ -726,16 +736,22 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<h1>Gopherbook</h1>
|
<div style="display: flex;">
|
||||||
<div id="userInfo" class="hidden">
|
<img src="/static/images/pngs/CutePose2.png" alt="on book" width="67px" height="75px">
|
||||||
<button onclick="logout()" class="secondary-btn" style="width: auto; padding: 8px 16px;">Logout</button>
|
<h1>Gopherbook</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="userInfo" class="hidden">
|
||||||
|
<button onclick="logout()" class="secondary-btn" style="width: auto; padding: 8px 16px;">Logout</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="authSection" class="auth-section">
|
<div id="authSection" class="auth-section">
|
||||||
<div id="authMessage" class="message hidden"></div>
|
<div id="authMessage" class="message hidden"></div>
|
||||||
|
<div style="display: flex; justify-content: center; margin: 0px 10px 20px 10px;">
|
||||||
|
<img src="/static/images/pngs/LogoPose2.png" alt="wink" style="width: 100px; height auto;">
|
||||||
|
</div>
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab active" onclick="showTab('login')">Login</button>
|
<button class="tab active" onclick="showTab('login')">Login</button>
|
||||||
<button class="tab" onclick="showTab('register')">Register</button>
|
<button class="tab" onclick="showTab('register')">Register</button>
|
||||||
|
|
@ -859,7 +875,7 @@
|
||||||
<h4 style="font-size: 14px; color: #8b949e; margin-bottom: 12px;">Create New Tag</h4>
|
<h4 style="font-size: 14px; color: #8b949e; margin-bottom: 12px;">Create New Tag</h4>
|
||||||
<div class="add-tag-form">
|
<div class="add-tag-form">
|
||||||
<input type="text" id="newTagName" class="add-tag-input" placeholder="Tag name">
|
<input type="text" id="newTagName" class="add-tag-input" placeholder="Tag name">
|
||||||
<input type="color" id="newTagColor" class="color-picker" value="#1f6feb">
|
<input type="color" id="newTagColor" class="color-picker" value="#446B6E">
|
||||||
<button onclick="createTag()" style="width: auto; padding: 8px 16px;">Add</button>
|
<button onclick="createTag()" style="width: auto; padding: 8px 16px;">Add</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1052,7 +1068,7 @@
|
||||||
|
|
||||||
selectedTags.forEach(function(tagName) {
|
selectedTags.forEach(function(tagName) {
|
||||||
var tag = allTags.find(function(t) { return t.name === tagName; });
|
var tag = allTags.find(function(t) { return t.name === tagName; });
|
||||||
var color = tag ? tag.color : '#1f6feb';
|
var color = tag ? tag.color : '#446B6E';
|
||||||
var filter = document.createElement('div');
|
var filter = document.createElement('div');
|
||||||
filter.className = 'tag-filter';
|
filter.className = 'tag-filter';
|
||||||
filter.style.background = color;
|
filter.style.background = color;
|
||||||
|
|
@ -1200,7 +1216,7 @@
|
||||||
tagsHTML = '<div class="comic-tags">';
|
tagsHTML = '<div class="comic-tags">';
|
||||||
comic.tags.forEach(tagName => {
|
comic.tags.forEach(tagName => {
|
||||||
const tag = allTags.find(t => t.name === tagName);
|
const tag = allTags.find(t => t.name === tagName);
|
||||||
const color = tag ? tag.color : '#1f6feb';
|
const color = tag ? tag.color : '#446B6E';
|
||||||
tagsHTML += '<span class="comic-tag" style="background: ' + color + '">' + tagName + '</span>';
|
tagsHTML += '<span class="comic-tag" style="background: ' + color + '">' + tagName + '</span>';
|
||||||
});
|
});
|
||||||
tagsHTML += '</div>';
|
tagsHTML += '</div>';
|
||||||
|
|
@ -1279,7 +1295,7 @@
|
||||||
|
|
||||||
managingComic.tags.forEach(tagName => {
|
managingComic.tags.forEach(tagName => {
|
||||||
const tag = allTags.find(t => t.name === tagName);
|
const tag = allTags.find(t => t.name === tagName);
|
||||||
const color = tag ? tag.color : '#1f6feb';
|
const color = tag ? tag.color : '#446B6E';
|
||||||
|
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = 'tag-item';
|
item.className = 'tag-item';
|
||||||
|
|
@ -1367,7 +1383,7 @@
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
document.getElementById('newTagName').value = '';
|
document.getElementById('newTagName').value = '';
|
||||||
document.getElementById('newTagColor').value = '#1f6feb';
|
document.getElementById('newTagColor').value = '#446B6E';
|
||||||
loadTags();
|
loadTags();
|
||||||
renderAvailableTags();
|
renderAvailableTags();
|
||||||
showMessage('Tag created!', 'success');
|
showMessage('Tag created!', 'success');
|
||||||
|
|
@ -1986,6 +2002,30 @@
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
console.error('Initial comics fetch failed:', err);
|
console.error('Initial comics fetch failed:', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add Enter key support for login form
|
||||||
|
document.getElementById('loginUsername').addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
login();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById('loginPassword').addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
login();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Enter key support for register form
|
||||||
|
document.getElementById('regUsername').addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
register();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.getElementById('regPassword').addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
register();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
BIN
docs/images/gopherbook-title.png
Normal file
BIN
docs/images/gopherbook-title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
10
docs/images/svgs/PIL.svg
Normal file
10
docs/images/svgs/PIL.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="28">
|
||||||
|
<g shape-rendering="crispEdges">
|
||||||
|
<rect width="75" height="28" fill="#555"/>
|
||||||
|
<rect x="75" width="39" height="28" fill="#07124A"/>
|
||||||
|
</g>
|
||||||
|
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="10" font-weight="bold">
|
||||||
|
<text x="37" y="18">LICENSE</text>
|
||||||
|
<text x="94" y="18">PIL</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 447 B |
62
scripts-bash/build-release.sh
Executable file
62
scripts-bash/build-release.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IMAGE_NAME="localhost/gopherbook-builder:latest"
|
||||||
|
CONTAINER_NAME="gopherbook-builder-tmp"
|
||||||
|
OUTPUT_DIR="./binaries"
|
||||||
|
|
||||||
|
echo "=== Building cross-compilation container ==="
|
||||||
|
podman build --force-rm -t "$IMAGE_NAME" -f Containerfile.build .
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Image build failed. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Creating temporary container ==="
|
||||||
|
podman create --name "$CONTAINER_NAME" "$IMAGE_NAME"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Creating output directory ==="
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
rm -f "$OUTPUT_DIR"/*
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Extracting Linux binary ==="
|
||||||
|
podman cp "$CONTAINER_NAME:/app/bin/gopherbook-linux" "$OUTPUT_DIR/gopherbook-linux"
|
||||||
|
chmod +x "$OUTPUT_DIR/gopherbook-linux"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Extracting Windows binary ==="
|
||||||
|
podman cp "$CONTAINER_NAME:/app/bin/gopherbook-windows.exe" "$OUTPUT_DIR/gopherbook-windows.exe"
|
||||||
|
chmod +x "$OUTPUT_DIR/gopherbook-windows.exe"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Cleaning up temporary container ==="
|
||||||
|
podman rm "$CONTAINER_NAME"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Build complete! ==="
|
||||||
|
echo "Binaries are in: $OUTPUT_DIR/"
|
||||||
|
ls -lh "$OUTPUT_DIR/"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Binary sizes ==="
|
||||||
|
du -h "$OUTPUT_DIR"/*
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Cleaning up builder image ==="
|
||||||
|
podman rmi "$IMAGE_NAME"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✓ Done! Your binaries are ready:"
|
||||||
|
echo " • Linux: $OUTPUT_DIR/gopherbook-linux"
|
||||||
|
echo " • Windows: $OUTPUT_DIR/gopherbook-windows.exe"
|
||||||
|
echo ""
|
||||||
|
echo "To run the Linux binary:"
|
||||||
|
echo " $OUTPUT_DIR/gopherbook-linux"
|
||||||
|
echo ""
|
||||||
|
echo "To test the Windows binary (requires wine):"
|
||||||
|
echo " wine $OUTPUT_DIR/gopherbook-windows.exe"
|
||||||
161
scripts-bash/package-release.sh
Executable file
161
scripts-bash/package-release.sh
Executable file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
VERSION="${1:-v1.3.000}"
|
||||||
|
RELEASE_DIR="./releases"
|
||||||
|
BINARIES_DIR="./binaries"
|
||||||
|
|
||||||
|
echo "=== Packaging Gopherbook $VERSION ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Create release directory
|
||||||
|
mkdir -p "$RELEASE_DIR"
|
||||||
|
|
||||||
|
# Clean old releases for this version
|
||||||
|
rm -f "$RELEASE_DIR"/gopherbook-$VERSION-*
|
||||||
|
|
||||||
|
# Check if binaries exist
|
||||||
|
if [ ! -f "$BINARIES_DIR/gopherbook-linux" ] || [ ! -f "$BINARIES_DIR/gopherbook-windows.exe" ]; then
|
||||||
|
echo "Error: Binaries not found. Run ./build-and-extract.sh first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Creating README.txt ==="
|
||||||
|
cat > /tmp/README.txt << 'EOF'
|
||||||
|
Gopherbook - Comic Book Reader (CBZ/CBT)
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
Quick Start:
|
||||||
|
------------
|
||||||
|
1. Run the gopherbook executable
|
||||||
|
2. Open your browser to http://localhost:8080
|
||||||
|
3. Register a new user account
|
||||||
|
4. Upload your CBZ/CBT comic files
|
||||||
|
|
||||||
|
Features:
|
||||||
|
---------
|
||||||
|
• Supports CBZ (ZIP) and CBT (TAR) formats
|
||||||
|
• Password-protected archives
|
||||||
|
• Tag management and filtering
|
||||||
|
• Bookmark pages
|
||||||
|
• Auto-organize by artist and story arc
|
||||||
|
• Watch folder for automatic imports
|
||||||
|
• Multi-user support with admin controls
|
||||||
|
|
||||||
|
Watch Folder:
|
||||||
|
-------------
|
||||||
|
Place CBZ/CBT files in the ./watch/<username>/ directory
|
||||||
|
and they will be automatically imported to your library.
|
||||||
|
|
||||||
|
Directory Structure:
|
||||||
|
--------------------
|
||||||
|
./library/ - Your comic library (per user)
|
||||||
|
./cache/ - Cover image cache
|
||||||
|
./etc/ - User data and settings
|
||||||
|
./watch/ - Watch folders for auto-import
|
||||||
|
|
||||||
|
Default Port: 8080
|
||||||
|
|
||||||
|
For more information, visit:
|
||||||
|
https://github.com/riomoo/gopherbook
|
||||||
|
https://codeberg.org/riomoo/gofudge
|
||||||
|
https://gitgud.io/riomoo/gopherbook
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "=== Creating Linux package ==="
|
||||||
|
LINUX_DIR="/tmp/gopherbook-linux"
|
||||||
|
rm -rf "$LINUX_DIR"
|
||||||
|
mkdir -p "$LINUX_DIR"
|
||||||
|
|
||||||
|
# Copy Linux binary
|
||||||
|
cp "$BINARIES_DIR/gopherbook-linux" "$LINUX_DIR/gopherbook"
|
||||||
|
chmod +x "$LINUX_DIR/gopherbook"
|
||||||
|
|
||||||
|
# Copy documentation
|
||||||
|
cp /tmp/README.txt "$LINUX_DIR/"
|
||||||
|
|
||||||
|
# Create run script
|
||||||
|
cat > "$LINUX_DIR/run.sh" << 'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
echo "Starting Gopherbook..."
|
||||||
|
echo "Open your browser to: http://localhost:8080"
|
||||||
|
echo "Press Ctrl+C to stop"
|
||||||
|
echo ""
|
||||||
|
./gopherbook
|
||||||
|
EOF
|
||||||
|
chmod +x "$LINUX_DIR/run.sh"
|
||||||
|
|
||||||
|
# Package Linux
|
||||||
|
cd /tmp
|
||||||
|
tar -czf "$LINUX_DIR.tar.gz" gopherbook-linux/
|
||||||
|
cd - > /dev/null
|
||||||
|
mv "/tmp/gopherbook-linux.tar.gz" "$RELEASE_DIR/gopherbook-$VERSION-linux-amd64.tar.gz"
|
||||||
|
rm -rf "$LINUX_DIR"
|
||||||
|
|
||||||
|
echo "✓ Linux package created"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Creating Windows package ==="
|
||||||
|
WINDOWS_DIR="/tmp/gopherbook-windows"
|
||||||
|
rm -rf "$WINDOWS_DIR"
|
||||||
|
mkdir -p "$WINDOWS_DIR"
|
||||||
|
|
||||||
|
# Copy Windows binary
|
||||||
|
cp "$BINARIES_DIR/gopherbook-windows.exe" "$WINDOWS_DIR/gopherbook.exe"
|
||||||
|
|
||||||
|
# Copy documentation (Windows line endings)
|
||||||
|
unix2dos < /tmp/README.txt > "$WINDOWS_DIR/README.txt" 2>/dev/null || cp /tmp/README.txt "$WINDOWS_DIR/README.txt"
|
||||||
|
|
||||||
|
# Create batch file
|
||||||
|
cat > "$WINDOWS_DIR/run.bat" << 'EOF'
|
||||||
|
@echo off
|
||||||
|
echo Starting Gopherbook...
|
||||||
|
echo Open your browser to: http://localhost:8080
|
||||||
|
echo Press Ctrl+C to stop
|
||||||
|
echo.
|
||||||
|
gopherbook.exe
|
||||||
|
pause
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create PowerShell script
|
||||||
|
cat > "$WINDOWS_DIR/run.ps1" << 'EOF'
|
||||||
|
Write-Host "Starting Gopherbook..." -ForegroundColor Green
|
||||||
|
Write-Host "Open your browser to: http://localhost:8080" -ForegroundColor Cyan
|
||||||
|
Write-Host "Press Ctrl+C to stop" -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
.\gopherbook.exe
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Package Windows
|
||||||
|
cd /tmp
|
||||||
|
zip -q -r "$WINDOWS_DIR.zip" gopherbook-windows/
|
||||||
|
cd - > /dev/null
|
||||||
|
mv "/tmp/gopherbook-windows.zip" "$RELEASE_DIR/gopherbook-$VERSION-windows-amd64.zip"
|
||||||
|
rm -rf "$WINDOWS_DIR"
|
||||||
|
|
||||||
|
echo "✓ Windows package created"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Creating checksums ==="
|
||||||
|
cd "$RELEASE_DIR"
|
||||||
|
sha256sum gopherbook-$VERSION-*.tar.gz gopherbook-$VERSION-*.zip > gopherbook-$VERSION-checksums.txt
|
||||||
|
cd - > /dev/null
|
||||||
|
|
||||||
|
echo "✓ Checksums created"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Release packages ready! ==="
|
||||||
|
echo ""
|
||||||
|
ls -lh "$RELEASE_DIR"/gopherbook-$VERSION-*
|
||||||
|
echo ""
|
||||||
|
echo "Release files:"
|
||||||
|
echo " • $RELEASE_DIR/gopherbook-$VERSION-linux-amd64.tar.gz"
|
||||||
|
echo " • $RELEASE_DIR/gopherbook-$VERSION-windows-amd64.zip"
|
||||||
|
echo " • $RELEASE_DIR/gopherbook-$VERSION-checksums.txt"
|
||||||
|
echo ""
|
||||||
|
echo "Upload these files to GitHub Releases!"
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
rm -f /tmp/README.txt
|
||||||
|
|
@ -41,6 +41,6 @@ echo "Cleaning up old images..."
|
||||||
podman image prune --force
|
podman image prune --force
|
||||||
|
|
||||||
echo "Update and cleanup complete!"
|
echo "Update and cleanup complete!"
|
||||||
echo "Container is running with memory limit: 512MB, swap: 512MB"
|
echo "Container is running with memory limit: 512MB"
|
||||||
echo "Go memory limit (GOMEMLIMIT): 512MiB"
|
echo "Go memory limit (GOMEMLIMIT): 512MiB"
|
||||||
echo "Aggressive GC enabled (GOGC=50)"
|
echo "Aggressive GC enabled (GOGC=50)"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue