rebrand: MangaBM → BookmarkManager, add novel library support (#15)

Two intertwined changes — the rebrand and the novel library were developed on
the same branch because the novel UI plumbing is part of the new "Bookmark
Manager" wordmark in the web shell.

## What it does

- **Rebrand**: MangaBM → BookmarkManager across the Go module, compose stack,
  env vars, Traefik hostnames, container/image names, userscript storage
  prefixes (`mangabm:cache` → `bmgr:manga:cache`, `mangabm:queue` → `bmgr:manga:queue`),
  and docs.
- **Novel library**: same backend, two libraries. New `kind` column splits
  bookmarks into `manga` / `novel`; PUT validates it. Two userscripts:
  - `manga-bookmark.user.js` — unchanged behaviour, just stamps its own `kind`.
  - `novel-bookmark.user.js` — separate Violentmonkey install with adapters
    for **novelfull.com** (polled via headless browser — Cloudflare JS
    challenge) and **lightnovelworld.net** (polled via plain TLS).
- **Web UI**: library switch on the app shell. Login art, libswitch, and
  novel-site colours from the Cinder design snapshot.

## Plumbing

- `addedColumns` ALTER for `kind` runs on first start after upgrade; every
  pre-existing row is backfilled to `'manga'`. No manual SQL, no down-time.
- `ALLOWED_ORIGINS` gains the two novel sites.
- New `NOVEL_USERSCRIPT_PATH` env (default `/userscript/novel-bookmark.user.js`),
  bindmounted alongside the manga script.
- Traefik router names `mangabm*` → `bmapi*` / `bmweb*`.

## Test status

- `go test ./...` — green
- `node --test userscript/test/logic.test.js` — 34 pass
- `node --test userscript/test/novel-logic.test.js` — 11 pass
- `node --check` on both userscripts — clean

## Notes for the redeploy

.env keys were renamed (`MANGA_API_HOST` → `BOOKMARK_API_HOST`,
`MANGA_WEB_HOST` → `BOOKMARK_WEB_HOST`). Update DNS / Traefik labels on the
prod override before pulling, otherwise the public hostnames go dark.
See the redeploy instructions I'll post next to this PR.

Reviewed-on: #15
Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com>
Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
This commit was merged in pull request #15.
This commit is contained in:
2026-08-06 03:58:23 +07:00
committed by sulthan
parent c445762244
commit 4229c179b0
40 changed files with 2948 additions and 347 deletions
+20 -20
View File
@@ -9,16 +9,16 @@ Whole thing is ~5 minutes, most of it waiting on `docker build`. Order matters:
**back up before you pull.** A backup taken after a bad migration is a backup of
the damage.
Paths below assume the checkout is at `/opt/mangabm`; substitute your own. The
one absolute rule about paths: **backups live in `../mangabm-backups/`**, a
sibling of the project directory (`/opt/mangabm-backups`), never inside it. It
Paths below assume the checkout is at `/opt/bookmarkmanager`; substitute your own. The
one absolute rule about paths: **backups live in `../bookmarkmanager-backups/`**, a
sibling of the project directory (`/opt/bookmarkmanager-backups`), never inside it. It
sits outside the repo so `git pull`, `git clean -fd` and a bad `rm -rf` inside
the checkout cannot take the backups with them.
```
/opt/
├── mangabm/ <- the checkout (this repo)
└── mangabm-backups/ <- bookmarks-YYYYmmdd-HHMMSS.db
├── bookmarkmanager/ <- the checkout (this repo)
└── bookmarkmanager-backups/ <- bookmarks-YYYYmmdd-HHMMSS.db
```
---
@@ -26,12 +26,12 @@ the checkout cannot take the backups with them.
## 0. Preflight
```bash
cd /opt/mangabm
cd /opt/bookmarkmanager
# Both -f flags, every time. The prod override is not standalone.
COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
$COMPOSE ps # manga-api should be Up
$COMPOSE ps # bookmark-api should be Up
git status --short # expect empty
git log --oneline -1 # note this hash — it is your rollback target
df -h /var/lib/docker | tail -1 # a build needs room
@@ -44,9 +44,9 @@ dirty tree fails halfway and leaves you in a worse spot than either.
Create the backup directory once, and make sure it is a sibling, not a child:
```bash
mkdir -p ../mangabm-backups
BACKUP_DIR="$(cd .. && pwd)/mangabm-backups" # absolute — Docker needs it
echo "$BACKUP_DIR" # -> /opt/mangabm-backups
mkdir -p ../bookmarkmanager-backups
BACKUP_DIR="$(cd .. && pwd)/bookmarkmanager-backups" # absolute — Docker needs it
echo "$BACKUP_DIR" # -> /opt/bookmarkmanager-backups
```
---
@@ -59,7 +59,7 @@ prefixes it with the project directory:
```bash
docker volume ls --filter name=bookmarks-data
# -> local mangabm_bookmarks-data
# -> local bookmarkmanager_bookmarks-data
VOL=$(docker volume ls --filter name=bookmarks-data -q | head -1)
```
@@ -172,11 +172,11 @@ bindmounted read-only and read fresh per request.
```bash
$COMPOSE ps # Up, and recently (re)created
docker logs manga-api --tail 20 # -> "listening on :8080 ..."
docker logs bookmark-api --tail 20 # -> "listening on :8080 ..."
```
Nothing in the log about the database or the poller failing. The image is tagged
`mangabm-backend:latest`, so the previous image is still on disk untagged —
`bookmarkmanager-backend:latest`, so the previous image is still on disk untagged —
that is what makes the rollback in §6 quick.
---
@@ -186,8 +186,8 @@ that is what makes the rollback in §6 quick.
Same four API checks as `DEPLOY.md` §3, plus the web UI. Set the host names once:
```bash
API=https://manga-api.violetcrown.my.id
WEB=https://manga.violetcrown.my.id
API=https://bookmark-api.violetcrown.my.id
WEB=https://bookmark.violetcrown.my.id
TOKEN=$(grep -E '^API_TOKEN=' .env | cut -d= -f2)
curl -s $API/healthz # -> ok
@@ -281,7 +281,7 @@ docker run --rm -v "$VOL":/data -v "$BACKUP_DIR":/backup alpine sh -c '
ls -l /data'
$COMPOSE start
docker logs manga-api --tail 20
docker logs bookmark-api --tail 20
curl -s -H "Authorization: Bearer $TOKEN" $API/bookmarks | head -c 200
```
@@ -302,9 +302,9 @@ Two steps here are easy to skip and both bite:
For a routine redeploy where nothing needs deciding:
```bash
cd /opt/mangabm
cd /opt/bookmarkmanager
COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
BACKUP_DIR="$(cd .. && pwd)/mangabm-backups"; mkdir -p "$BACKUP_DIR"
BACKUP_DIR="$(cd .. && pwd)/bookmarkmanager-backups"; mkdir -p "$BACKUP_DIR"
VOL=$(docker volume ls --filter name=bookmarks-data -q | head -1)
STAMP=$(date -u +%Y%m%d-%H%M%S)
@@ -314,7 +314,7 @@ docker run --rm -v "$VOL":/data -v "$BACKUP_DIR":/backup alpine sh -c \
git pull --ff-only &&
$COMPOSE up -d --build &&
sleep 5 &&
curl -sf https://manga-api.violetcrown.my.id/healthz && echo " deploy ok"
curl -sf https://bookmark-api.violetcrown.my.id/healthz && echo " deploy ok"
```
The `&&` chain is deliberate: if the backup or its integrity check fails,
@@ -332,7 +332,7 @@ command can tell you the panel works on the phone.
| CSS or template change did not appear | You restarted without `--build`. Assets are `//go:embed`ed. |
| Font answers `application/octet-stream` | Old binary — the `.woff2` MIME registration is in `web.go`. Rebuild. |
| Everyone logged out of the web UI | `API_TOKEN` or `WEB_PASSWORD` changed; sessions are derived from both. Expected, just log in again. |
| `compose` errors about `MANGA_WEB_HOST` | Run from the directory holding `.env`. Both host vars are required even when the web UI is unused. |
| `compose` errors about `BOOKMARK_WEB_HOST` | Run from the directory holding `.env`. Both host vars are required even when the web UI is unused. |
| Userscript did not update on the phone | Violentmonkey polls on its own schedule; force a check. `@version` comes from the file's mtime, so confirm the pull actually touched it. |
| `apk add sqlite` fails (no network) | Use the cold-copy fallback in §1 — and copy `bookmarks.db-wal` too. |
| Reads work but every write fails after a restore | Restored file is root-owned; the container is uid 65532. `chown 65532:65532` it (§6). |