rebrand: MangaBM → BookmarkManager, add novel library support #15
+4
-4
@@ -10,7 +10,7 @@ ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicsca
|
|||||||
|
|
||||||
# --- Prod override (Traefik) only ---
|
# --- Prod override (Traefik) only ---
|
||||||
# Subdomain Traefik routes to this service (required by the prod override).
|
# Subdomain Traefik routes to this service (required by the prod override).
|
||||||
# MANGA_API_HOST=manga-api.example.com
|
# BOOKMARK_API_HOST=bookmark-api.example.com
|
||||||
# Traefik's docker network name, if not "proxy".
|
# Traefik's docker network name, if not "proxy".
|
||||||
# PROXY_NETWORK=proxy
|
# PROXY_NETWORK=proxy
|
||||||
# Traefik HTTPS entrypoint + cert resolver names, if yours differ from these.
|
# Traefik HTTPS entrypoint + cert resolver names, if yours differ from these.
|
||||||
@@ -18,7 +18,7 @@ ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicsca
|
|||||||
# TRAEFIK_CERTRESOLVER=le
|
# TRAEFIK_CERTRESOLVER=le
|
||||||
|
|
||||||
# --- Web UI ---
|
# --- Web UI ---
|
||||||
# Password for the browser UI at https://$MANGA_WEB_HOST. Leave unset to
|
# Password for the browser UI at https://$BOOKMARK_WEB_HOST. Leave unset to
|
||||||
# disable the web UI entirely (the routes are not registered at all).
|
# disable the web UI entirely (the routes are not registered at all).
|
||||||
# Generate one: openssl rand -base64 18
|
# Generate one: openssl rand -base64 18
|
||||||
WEB_PASSWORD=
|
WEB_PASSWORD=
|
||||||
@@ -27,8 +27,8 @@ WEB_PASSWORD=
|
|||||||
# whether or not WEB_PASSWORD is set). Left commented on purpose: an example
|
# whether or not WEB_PASSWORD is set). Left commented on purpose: an example
|
||||||
# value here would be a silent wrong-hostname fallback, and Traefik would
|
# value here would be a silent wrong-hostname fallback, and Traefik would
|
||||||
# publish the UI router on a domain you do not own. The same container also
|
# publish the UI router on a domain you do not own. The same container also
|
||||||
# answers on MANGA_API_HOST for the userscript's API.
|
# answers on BOOKMARK_API_HOST for the userscript's API.
|
||||||
# MANGA_WEB_HOST=manga.example.com
|
# BOOKMARK_WEB_HOST=bookmark.example.com
|
||||||
|
|
||||||
# --- Latest-chapter poller ---
|
# --- Latest-chapter poller ---
|
||||||
# The backend re-checks each bookmarked series' newest published chapter on its
|
# The backend re-checks each bookmarked series' newest published chapter on its
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Smoke test: `curl` endpoints with `Authorization: Bearer <token>`; confirm `OPTI
|
|||||||
## Design system
|
## Design system
|
||||||
|
|
||||||
Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md`
|
Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md`
|
||||||
— source of truth Claude Design project `mangaBookmark Web UI`
|
— source of truth Claude Design project `BookmarkManager Web UI`
|
||||||
(`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching
|
(`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching
|
||||||
`backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript
|
`backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript
|
||||||
`TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other
|
`TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Smoke test: `curl` endpoints with `Authorization: Bearer <token>`; confirm `OPTI
|
|||||||
## Design system
|
## Design system
|
||||||
|
|
||||||
Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md`
|
Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md`
|
||||||
— source of truth Claude Design project `mangaBookmark Web UI`
|
— source of truth Claude Design project `BookmarkManager Web UI`
|
||||||
(`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching
|
(`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching
|
||||||
`backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript
|
`backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript
|
||||||
`TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other
|
`TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ ACME/cert resolver, and control a domain.
|
|||||||
|
|
||||||
- Docker + Docker Compose on the server.
|
- Docker + Docker Compose on the server.
|
||||||
- A Traefik instance watching a Docker network (default name assumed: `proxy`).
|
- A Traefik instance watching a Docker network (default name assumed: `proxy`).
|
||||||
- DNS: an `A`/`AAAA` record for `manga-api.<yourdomain>` pointing at the server.
|
- DNS: an `A`/`AAAA` record for `bookmark-api.<yourdomain>` pointing at the server.
|
||||||
- The repo copied to the server, e.g. `/opt/mangabm/` (needs `backend/`,
|
- The repo copied to the server, e.g. `/opt/bookmarkmanager/` (needs `backend/`,
|
||||||
`docker-compose.yml`, `docker-compose.prod.yml`, `.env.example`).
|
`docker-compose.yml`, `docker-compose.prod.yml`, `.env.example`).
|
||||||
|
|
||||||
Confirm the Traefik network exists (create if not):
|
Confirm the Traefik network exists (create if not):
|
||||||
@@ -25,7 +25,7 @@ docker network ls | grep proxy || docker network create proxy
|
|||||||
## 1. Configure `.env`
|
## 1. Configure `.env`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/mangabm
|
cd /opt/bookmarkmanager
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -39,10 +39,10 @@ API_TOKEN=<paste output of: openssl rand -hex 32>
|
|||||||
ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to
|
ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to
|
||||||
|
|
||||||
# Required for the Traefik override. Both have no fallback — compose refuses
|
# Required for the Traefik override. Both have no fallback — compose refuses
|
||||||
# to start without them. MANGA_WEB_HOST is required even if you never set
|
# to start without them. BOOKMARK_WEB_HOST is required even if you never set
|
||||||
# WEB_PASSWORD; see 1b.
|
# WEB_PASSWORD; see 1b.
|
||||||
MANGA_API_HOST=manga-api.violetcrown.my.id
|
BOOKMARK_API_HOST=bookmark-api.violetcrown.my.id
|
||||||
MANGA_WEB_HOST=manga.violetcrown.my.id
|
BOOKMARK_WEB_HOST=bookmark.violetcrown.my.id
|
||||||
|
|
||||||
# Only if your Traefik setup differs from these defaults:
|
# Only if your Traefik setup differs from these defaults:
|
||||||
# PROXY_NETWORK=proxy
|
# PROXY_NETWORK=proxy
|
||||||
@@ -67,13 +67,13 @@ grep -E '^API_TOKEN=' .env # copy this — the userscript needs the same value
|
|||||||
|
|
||||||
The browser UI is served by the same container on a second hostname.
|
The browser UI is served by the same container on a second hostname.
|
||||||
|
|
||||||
1. Add a DNS `A`/`AAAA` record for `manga.<yourdomain>` pointing at the server —
|
1. Add a DNS `A`/`AAAA` record for `bookmark.<yourdomain>` pointing at the server —
|
||||||
the same address as `manga-api.<yourdomain>`.
|
the same address as `bookmark-api.<yourdomain>`.
|
||||||
|
|
||||||
2. Set both variables in `.env`:
|
2. Set both variables in `.env`:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
MANGA_WEB_HOST=manga.violetcrown.my.id
|
BOOKMARK_WEB_HOST=bookmark.violetcrown.my.id
|
||||||
WEB_PASSWORD=<paste output of: openssl rand -base64 18>
|
WEB_PASSWORD=<paste output of: openssl rand -base64 18>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,16 +88,16 @@ The browser UI is served by the same container on a second hostname.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
||||||
curl -s -o /dev/null -w '%{http_code}\n' https://manga.violetcrown.my.id/
|
curl -s -o /dev/null -w '%{http_code}\n' https://bookmark.violetcrown.my.id/
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected `200`, serving the login page.
|
Expected `200`, serving the login page.
|
||||||
|
|
||||||
Leaving `WEB_PASSWORD` unset is safe: the web routes are not registered and `/`
|
Leaving `WEB_PASSWORD` unset is safe: the web routes are not registered and `/`
|
||||||
returns 404. The userscript's API on `MANGA_API_HOST` is unaffected either way.
|
returns 404. The userscript's API on `BOOKMARK_API_HOST` is unaffected either way.
|
||||||
|
|
||||||
`MANGA_WEB_HOST` itself is required by the prod override regardless — like
|
`BOOKMARK_WEB_HOST` itself is required by the prod override regardless — like
|
||||||
`MANGA_API_HOST`, its Traefik label has no fallback, so `docker compose up`
|
`BOOKMARK_API_HOST`, its Traefik label has no fallback, so `docker compose up`
|
||||||
refuses to start without it even if `WEB_PASSWORD` is unset and the web UI is
|
refuses to start without it even if `WEB_PASSWORD` is unset and the web UI is
|
||||||
otherwise dormant.
|
otherwise dormant.
|
||||||
|
|
||||||
@@ -116,9 +116,9 @@ This merges the base file (build/image/env/volume) with the prod override
|
|||||||
(no host port, Traefik network + router labels). Always pass **both** `-f`
|
(no host port, Traefik network + router labels). Always pass **both** `-f`
|
||||||
flags — the prod file is not standalone.
|
flags — the prod file is not standalone.
|
||||||
|
|
||||||
Two services come up: `manga-api` (the backend) and `headless-shell`, a CDP
|
Two services come up: `bookmark-api` (the backend) and `headless-shell`, a CDP
|
||||||
sidecar the poller uses to fetch kagane (behind a Cloudflare JS challenge).
|
sidecar the poller uses to fetch kagane (behind a Cloudflare JS challenge).
|
||||||
It has no published port — only `manga-api` can reach it, over
|
It has no published port — only `bookmark-api` can reach it, over
|
||||||
`BROWSER_WS_URL`. Missing or unreachable, the poller just skips kagane and
|
`BROWSER_WS_URL`. Missing or unreachable, the poller just skips kagane and
|
||||||
logs it; nothing else is affected.
|
logs it; nothing else is affected.
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ Check it's up and healthy:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml ps
|
docker compose -f docker-compose.yml -f docker-compose.prod.yml ps
|
||||||
docker logs manga-api --tail 20 # expect: "listening on :8080 ..."
|
docker logs bookmark-api --tail 20 # expect: "listening on :8080 ..."
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -137,21 +137,21 @@ Give Traefik a few seconds to issue the cert, then:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Health (no auth) — must be valid TLS, no cert warning.
|
# Health (no auth) — must be valid TLS, no cert warning.
|
||||||
curl -s https://manga-api.violetcrown.my.id/healthz # -> ok
|
curl -s https://bookmark-api.violetcrown.my.id/healthz # -> ok
|
||||||
|
|
||||||
# Auth enforced.
|
# Auth enforced.
|
||||||
curl -s -o /dev/null -w '%{http_code}\n' \
|
curl -s -o /dev/null -w '%{http_code}\n' \
|
||||||
https://manga-api.violetcrown.my.id/bookmarks # -> 401
|
https://bookmark-api.violetcrown.my.id/bookmarks # -> 401
|
||||||
|
|
||||||
TOKEN=$(grep -E '^API_TOKEN=' .env | cut -d= -f2)
|
TOKEN=$(grep -E '^API_TOKEN=' .env | cut -d= -f2)
|
||||||
curl -s -H "Authorization: Bearer $TOKEN" \
|
curl -s -H "Authorization: Bearer $TOKEN" \
|
||||||
https://manga-api.violetcrown.my.id/bookmarks # -> []
|
https://bookmark-api.violetcrown.my.id/bookmarks # -> []
|
||||||
|
|
||||||
# CORS preflight from a real site origin.
|
# CORS preflight from a real site origin.
|
||||||
curl -s -i -X OPTIONS \
|
curl -s -i -X OPTIONS \
|
||||||
-H 'Origin: https://asurascans.com' \
|
-H 'Origin: https://asurascans.com' \
|
||||||
-H 'Access-Control-Request-Method: PUT' \
|
-H 'Access-Control-Request-Method: PUT' \
|
||||||
https://manga-api.violetcrown.my.id/bookmarks/x | grep -i access-control
|
https://bookmark-api.violetcrown.my.id/bookmarks/x | grep -i access-control
|
||||||
# -> Access-Control-Allow-Origin: https://asurascans.com (+ Methods/Headers)
|
# -> Access-Control-Allow-Origin: https://asurascans.com (+ Methods/Headers)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ a bad cert makes the browser block the userscript's `fetch()` (mixed content).
|
|||||||
Edit the config block at the top of `userscript/manga-bookmark.user.js`:
|
Edit the config block at the top of `userscript/manga-bookmark.user.js`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const API_BASE = "https://manga-api.yourdomain.com"; // no trailing slash
|
const API_BASE = "https://bookmark-api.yourdomain.com"; // no trailing slash
|
||||||
const API_TOKEN = "<same token as .env>";
|
const API_TOKEN = "<same token as .env>";
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ Tampermonkey/Violentmonkey for quick checks before going mobile.
|
|||||||
## 6. Smoke-test the full loop
|
## 6. Smoke-test the full loop
|
||||||
|
|
||||||
1. Bookmark a series on Asura.
|
1. Bookmark a series on Asura.
|
||||||
2. `curl -s -H "Authorization: Bearer $TOKEN" https://manga-api.yourdomain.com/bookmarks`
|
2. `curl -s -H "Authorization: Bearer $TOKEN" https://bookmark-api.yourdomain.com/bookmarks`
|
||||||
on the server — the series should appear.
|
on the server — the series should appear.
|
||||||
3. Open a chapter of that series — reopen the panel; last-read updates to that
|
3. Open a chapter of that series — reopen the panel; last-read updates to that
|
||||||
chapter (auto, never regresses on older chapters).
|
chapter (auto, never regresses on older chapters).
|
||||||
@@ -223,7 +223,7 @@ SQLite data persists in the named volume `bookmarks-data` across rebuilds.
|
|||||||
| Symptom | Likely cause / fix |
|
| Symptom | Likely cause / fix |
|
||||||
|---------|--------------------|
|
|---------|--------------------|
|
||||||
| No cert / TLS error at the domain | `TRAEFIK_ENTRYPOINT` or `TRAEFIK_CERTRESOLVER` name wrong; or DNS not resolving yet. Check `docker logs <traefik>`. |
|
| No cert / TLS error at the domain | `TRAEFIK_ENTRYPOINT` or `TRAEFIK_CERTRESOLVER` name wrong; or DNS not resolving yet. Check `docker logs <traefik>`. |
|
||||||
| 404 from Traefik | Service not on the `proxy` network, or `MANGA_API_HOST` mismatch. Confirm `docker network inspect proxy` lists `manga-api`. |
|
| 404 from Traefik | Service not on the `proxy` network, or `BOOKMARK_API_HOST` mismatch. Confirm `docker network inspect proxy` lists `bookmark-api`. |
|
||||||
| `fetch` fails in the userscript, `curl` works | Origin missing from `ALLOWED_ORIGINS`, or mixed content (backend not HTTPS). |
|
| `fetch` fails in the userscript, `curl` works | Origin missing from `ALLOWED_ORIGINS`, or mixed content (backend not HTTPS). |
|
||||||
| 401 with the right token | Trailing space/newline in `API_TOKEN`; regenerate and restart. |
|
| 401 with the right token | Trailing space/newline in `API_TOKEN`; regenerate and restart. |
|
||||||
| Panel button absent | URL didn't match an adapter, or user scripts disabled in Bromite. |
|
| Panel button absent | URL didn't match an adapter, or user scripts disabled in Bromite. |
|
||||||
@@ -243,7 +243,7 @@ auto-updates come from it too.
|
|||||||
Install once, on the phone (Cromite + Violentmonkey):
|
Install once, on the phone (Cromite + Violentmonkey):
|
||||||
|
|
||||||
```
|
```
|
||||||
https://manga-api.<your-domain>/u/<API_TOKEN>/manga-bookmark.user.js
|
https://bookmark-api.<your-domain>/u/<API_TOKEN>/manga-bookmark.user.js
|
||||||
```
|
```
|
||||||
|
|
||||||
Open that URL in Cromite; Violentmonkey offers to install it. The token is in
|
Open that URL in Cromite; Violentmonkey offers to install it. The token is in
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ Not a public reading tracker or social app — a private, self-hosted sync layer
|
|||||||
|
|
||||||
## Brand Commitments
|
## Brand Commitments
|
||||||
|
|
||||||
- Name: **mangaBookmark**.
|
- Name: **BookmarkManager**.
|
||||||
- **Dark-first is binding**: current dark-by-default / light-follows-system-preference behavior must be preserved as a design constraint, not just a starting default, because reading happens at night.
|
- **Dark-first is binding**: current dark-by-default / light-follows-system-preference behavior must be preserved as a design constraint, not just a starting default, because reading happens at night.
|
||||||
|
|
||||||
## Evidence on Hand
|
## Evidence on Hand
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ curl -s -i -X OPTIONS -H 'Origin: https://asurascans.com' \
|
|||||||
|
|
||||||
### Deploy behind your reverse proxy
|
### Deploy behind your reverse proxy
|
||||||
|
|
||||||
Route `https://manga-api.<domain>` → the service on `:8080` (TLS at the proxy).
|
Route `https://bookmark-api.<domain>` → the service on `:8080` (TLS at the proxy).
|
||||||
|
|
||||||
- **Host proxy** (nginx/Caddy on the host): the base compose already binds
|
- **Host proxy** (nginx/Caddy on the host): the base compose already binds
|
||||||
`127.0.0.1:8080`; point the proxy `proxy_pass http://127.0.0.1:8080;`.
|
`127.0.0.1:8080`; point the proxy `proxy_pass http://127.0.0.1:8080;`.
|
||||||
@@ -99,7 +99,7 @@ Route `https://manga-api.<domain>` → the service on `:8080` (TLS at the proxy)
|
|||||||
```
|
```
|
||||||
Set `PROXY_NETWORK` in `.env` if your network isn't named `proxy`.
|
Set `PROXY_NETWORK` in `.env` if your network isn't named `proxy`.
|
||||||
|
|
||||||
Verify: `https://manga-api.<domain>/healthz` returns `ok` over valid TLS (no
|
Verify: `https://bookmark-api.<domain>/healthz` returns `ok` over valid TLS (no
|
||||||
mixed-content), and an `OPTIONS` preflight from a real site origin returns the
|
mixed-content), and an `OPTIONS` preflight from a real site origin returns the
|
||||||
CORS headers.
|
CORS headers.
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ CORS headers.
|
|||||||
Edit the config block at the top of `userscript/manga-bookmark.user.js`:
|
Edit the config block at the top of `userscript/manga-bookmark.user.js`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const API_BASE = "https://manga-api.<domain>"; // no trailing slash
|
const API_BASE = "https://bookmark-api.<domain>"; // no trailing slash
|
||||||
const API_TOKEN = "<same token as backend>";
|
const API_TOKEN = "<same token as backend>";
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ userscript does the looking, from your own browser session:
|
|||||||
(`LATEST_CHECK_BATCH` / `LATEST_CHECK_THROTTLE_MS`). Failures are silent and
|
(`LATEST_CHECK_BATCH` / `LATEST_CHECK_THROTTLE_MS`). Failures are silent and
|
||||||
simply retried after the window.
|
simply retried after the window.
|
||||||
|
|
||||||
Freshness is tracked per device in `localStorage` under `mangabm:lastchecked`
|
Freshness is tracked per device in `localStorage` under `bmgr:manga:lastchecked`
|
||||||
and is deliberately not synced, since each device checks on its own.
|
and is deliberately not synced, since each device checks on its own.
|
||||||
|
|
||||||
This means a bookmark is as current as its last check — not the moment a
|
This means a bookmark is as current as its last check — not the moment a
|
||||||
|
|||||||
+20
-20
@@ -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
|
**back up before you pull.** A backup taken after a bad migration is a backup of
|
||||||
the damage.
|
the damage.
|
||||||
|
|
||||||
Paths below assume the checkout is at `/opt/mangabm`; substitute your own. The
|
Paths below assume the checkout is at `/opt/bookmarkmanager`; substitute your own. The
|
||||||
one absolute rule about paths: **backups live in `../mangabm-backups/`**, a
|
one absolute rule about paths: **backups live in `../bookmarkmanager-backups/`**, a
|
||||||
sibling of the project directory (`/opt/mangabm-backups`), never inside it. It
|
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
|
sits outside the repo so `git pull`, `git clean -fd` and a bad `rm -rf` inside
|
||||||
the checkout cannot take the backups with them.
|
the checkout cannot take the backups with them.
|
||||||
|
|
||||||
```
|
```
|
||||||
/opt/
|
/opt/
|
||||||
├── mangabm/ <- the checkout (this repo)
|
├── bookmarkmanager/ <- the checkout (this repo)
|
||||||
└── mangabm-backups/ <- bookmarks-YYYYmmdd-HHMMSS.db
|
└── bookmarkmanager-backups/ <- bookmarks-YYYYmmdd-HHMMSS.db
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -26,12 +26,12 @@ the checkout cannot take the backups with them.
|
|||||||
## 0. Preflight
|
## 0. Preflight
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/mangabm
|
cd /opt/bookmarkmanager
|
||||||
|
|
||||||
# Both -f flags, every time. The prod override is not standalone.
|
# Both -f flags, every time. The prod override is not standalone.
|
||||||
COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
|
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 status --short # expect empty
|
||||||
git log --oneline -1 # note this hash — it is your rollback target
|
git log --oneline -1 # note this hash — it is your rollback target
|
||||||
df -h /var/lib/docker | tail -1 # a build needs room
|
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:
|
Create the backup directory once, and make sure it is a sibling, not a child:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ../mangabm-backups
|
mkdir -p ../bookmarkmanager-backups
|
||||||
BACKUP_DIR="$(cd .. && pwd)/mangabm-backups" # absolute — Docker needs it
|
BACKUP_DIR="$(cd .. && pwd)/bookmarkmanager-backups" # absolute — Docker needs it
|
||||||
echo "$BACKUP_DIR" # -> /opt/mangabm-backups
|
echo "$BACKUP_DIR" # -> /opt/bookmarkmanager-backups
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -59,7 +59,7 @@ prefixes it with the project directory:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker volume ls --filter name=bookmarks-data
|
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)
|
VOL=$(docker volume ls --filter name=bookmarks-data -q | head -1)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -172,11 +172,11 @@ bindmounted read-only and read fresh per request.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
$COMPOSE ps # Up, and recently (re)created
|
$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
|
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.
|
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:
|
Same four API checks as `DEPLOY.md` §3, plus the web UI. Set the host names once:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
API=https://manga-api.violetcrown.my.id
|
API=https://bookmark-api.violetcrown.my.id
|
||||||
WEB=https://manga.violetcrown.my.id
|
WEB=https://bookmark.violetcrown.my.id
|
||||||
TOKEN=$(grep -E '^API_TOKEN=' .env | cut -d= -f2)
|
TOKEN=$(grep -E '^API_TOKEN=' .env | cut -d= -f2)
|
||||||
|
|
||||||
curl -s $API/healthz # -> ok
|
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'
|
ls -l /data'
|
||||||
|
|
||||||
$COMPOSE start
|
$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
|
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:
|
For a routine redeploy where nothing needs deciding:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/mangabm
|
cd /opt/bookmarkmanager
|
||||||
COMPOSE="docker compose -f docker-compose.yml -f docker-compose.prod.yml"
|
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)
|
VOL=$(docker volume ls --filter name=bookmarks-data -q | head -1)
|
||||||
STAMP=$(date -u +%Y%m%d-%H%M%S)
|
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 &&
|
git pull --ff-only &&
|
||||||
$COMPOSE up -d --build &&
|
$COMPOSE up -d --build &&
|
||||||
sleep 5 &&
|
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,
|
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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| `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). |
|
| Reads work but every write fails after a restore | Restored file is root-owned; the container is uid 65532. `chown 65532:65532` it (§6). |
|
||||||
|
|||||||
+15
-15
@@ -1,11 +1,11 @@
|
|||||||
# Production override: join an existing Traefik network and let Traefik route
|
# Production override: join an existing Traefik network and let Traefik route
|
||||||
# manga-api.<domain> -> this service with TLS. No host port published.
|
# bookmark-api.<domain> -> this service with TLS. No host port published.
|
||||||
#
|
#
|
||||||
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
||||||
#
|
#
|
||||||
# Set in .env:
|
# Set in .env:
|
||||||
# MANGA_API_HOST=manga-api.example.com # your subdomain (required)
|
# BOOKMARK_API_HOST=bookmark-api.example.com # your subdomain (required)
|
||||||
# MANGA_WEB_HOST=manga.example.com # browser UI subdomain, same container (required)
|
# BOOKMARK_WEB_HOST=bookmark.example.com # browser UI subdomain, same container (required)
|
||||||
# PROXY_NETWORK=proxy # Traefik's network name, if not "proxy"
|
# PROXY_NETWORK=proxy # Traefik's network name, if not "proxy"
|
||||||
# TRAEFIK_ENTRYPOINT=websecure # your HTTPS entrypoint name
|
# TRAEFIK_ENTRYPOINT=websecure # your HTTPS entrypoint name
|
||||||
# TRAEFIK_CERTRESOLVER=le # your ACME/cert resolver name
|
# TRAEFIK_CERTRESOLVER=le # your ACME/cert resolver name
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
# docker network create proxy # if it doesn't yet
|
# docker network create proxy # if it doesn't yet
|
||||||
|
|
||||||
services:
|
services:
|
||||||
manga-api:
|
bookmark-api:
|
||||||
# Traffic arrives over the Traefik network, not a published port.
|
# Traffic arrives over the Traefik network, not a published port.
|
||||||
ports: !reset []
|
ports: !reset []
|
||||||
environment:
|
environment:
|
||||||
@@ -33,19 +33,19 @@ services:
|
|||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.docker.network=${PROXY_NETWORK:-proxy}"
|
- "traefik.docker.network=${PROXY_NETWORK:-proxy}"
|
||||||
- "traefik.http.routers.mangabm.rule=Host(`${MANGA_API_HOST:?set MANGA_API_HOST in .env}`)"
|
- "traefik.http.routers.bmapi.rule=Host(`${BOOKMARK_API_HOST:?set BOOKMARK_API_HOST in .env}`)"
|
||||||
- "traefik.http.routers.mangabm.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
- "traefik.http.routers.bmapi.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||||
- "traefik.http.routers.mangabm.tls=true"
|
- "traefik.http.routers.bmapi.tls=true"
|
||||||
- "traefik.http.routers.mangabm.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}"
|
- "traefik.http.routers.bmapi.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}"
|
||||||
- "traefik.http.services.mangabm.loadbalancer.server.port=8080"
|
- "traefik.http.services.bmapi.loadbalancer.server.port=8080"
|
||||||
# Second hostname for the browser UI, same container. Traefik needs the
|
# Second hostname for the browser UI, same container. Traefik needs the
|
||||||
# service named explicitly once more than one router targets it.
|
# service named explicitly once more than one router targets it.
|
||||||
- "traefik.http.routers.mangabm.service=mangabm"
|
- "traefik.http.routers.bmapi.service=bmapi"
|
||||||
- "traefik.http.routers.mangaweb.rule=Host(`${MANGA_WEB_HOST:?set MANGA_WEB_HOST in .env}`)"
|
- "traefik.http.routers.bmweb.rule=Host(`${BOOKMARK_WEB_HOST:?set BOOKMARK_WEB_HOST in .env}`)"
|
||||||
- "traefik.http.routers.mangaweb.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
- "traefik.http.routers.bmweb.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
||||||
- "traefik.http.routers.mangaweb.tls=true"
|
- "traefik.http.routers.bmweb.tls=true"
|
||||||
- "traefik.http.routers.mangaweb.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}"
|
- "traefik.http.routers.bmweb.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}"
|
||||||
- "traefik.http.routers.mangaweb.service=mangabm"
|
- "traefik.http.routers.bmweb.service=bmapi"
|
||||||
|
|
||||||
# headless-shell is untouched here: it keeps its `browser` network membership
|
# headless-shell is untouched here: it keeps its `browser` network membership
|
||||||
# from the base file and must never join `proxy` — that network is shared
|
# from the base file and must never join `proxy` — that network is shared
|
||||||
|
|||||||
+6
-6
@@ -1,16 +1,16 @@
|
|||||||
# Base stack — works standalone for local smoke testing (`docker compose up`).
|
# Base stack — works standalone for local smoke testing (`docker compose up`).
|
||||||
# The service binds 127.0.0.1:8080; a host reverse proxy (nginx/Caddy/Traefik)
|
# The service binds 127.0.0.1:8080; a host reverse proxy (nginx/Caddy/Traefik)
|
||||||
# terminates TLS for manga-api.<domain> and forwards to it.
|
# terminates TLS for bookmark-api.<domain> and forwards to it.
|
||||||
#
|
#
|
||||||
# If your proxy runs in Docker on its own network, use the prod override which
|
# If your proxy runs in Docker on its own network, use the prod override which
|
||||||
# attaches to that network instead of publishing a port:
|
# attaches to that network instead of publishing a port:
|
||||||
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
services:
|
services:
|
||||||
manga-api:
|
bookmark-api:
|
||||||
build: ./backend
|
build: ./backend
|
||||||
image: mangabm-backend:latest
|
image: bookmarkmanager-backend:latest
|
||||||
container_name: manga-api
|
container_name: bookmark-api
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
# API_TOKEN is required — compose refuses to start without it.
|
# API_TOKEN is required — compose refuses to start without it.
|
||||||
@@ -63,7 +63,7 @@ services:
|
|||||||
# container's lifetime.
|
# container's lifetime.
|
||||||
init: true
|
init: true
|
||||||
# Deliberately no `ports:` — an exposed CDP endpoint is remote code
|
# Deliberately no `ports:` — an exposed CDP endpoint is remote code
|
||||||
# execution. Only manga-api, via the `browser` network below, may reach it.
|
# execution. Only bookmark-api, via the `browser` network below, may reach it.
|
||||||
# Don't pass --remote-debugging-address/--remote-debugging-port here: the
|
# Don't pass --remote-debugging-address/--remote-debugging-port here: the
|
||||||
# image's own entrypoint (/headless-shell/run.sh) already starts Chrome on
|
# image's own entrypoint (/headless-shell/run.sh) already starts Chrome on
|
||||||
# 127.0.0.1:9223 and fronts it with a socat proxy listening on 0.0.0.0:9222.
|
# 127.0.0.1:9223 and fronts it with a socat proxy listening on 0.0.0.0:9222.
|
||||||
@@ -86,7 +86,7 @@ volumes:
|
|||||||
|
|
||||||
networks:
|
networks:
|
||||||
# Not `internal: true`: headless Chrome still needs outbound access to reach
|
# Not `internal: true`: headless Chrome still needs outbound access to reach
|
||||||
# kagane.to. Isolation here comes from membership (only manga-api and
|
# kagane.to. Isolation here comes from membership (only bookmark-api and
|
||||||
# headless-shell join it), not from cutting egress.
|
# headless-shell join it), not from cutting egress.
|
||||||
browser:
|
browser:
|
||||||
ipam:
|
ipam:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Cinder — mangaBookmark design system
|
# Cinder — BookmarkManager design system
|
||||||
|
|
||||||
Source of truth: the Claude Design project **mangaBookmark Web UI**
|
Source of truth: the Claude Design project **BookmarkManager Web UI**
|
||||||
(`969ac210-fe02-4c01-ae1b-9a271dcc779a`, `index.html` + siblings
|
(`969ac210-fe02-4c01-ae1b-9a271dcc779a`, `index.html` + siblings
|
||||||
`archived.html`/`fav.html`/`finished.html`/`new.html`/`login.html`/`mobile.html`,
|
`archived.html`/`fav.html`/`finished.html`/`new.html`/`login.html`/`mobile.html`,
|
||||||
`style.css`, `filter.js`). This file records the rules that got implemented so
|
`style.css`, `filter.js`). This file records the rules that got implemented so
|
||||||
@@ -118,7 +118,7 @@ root is at the mercy of the host site's CSP.
|
|||||||
Recurring specs (copy these rather than inventing sizes):
|
Recurring specs (copy these rather than inventing sizes):
|
||||||
|
|
||||||
- Brand: `400 26px/1 display` (`30px` ≥720px), inline SVG mark (§4) + `<em>` in
|
- Brand: `400 26px/1 display` (`30px` ≥720px), inline SVG mark (§4) + `<em>` in
|
||||||
ember italic — `manga<em>Bookmark</em>`.
|
ember italic — `Bookmark<em>Manager</em>`.
|
||||||
- Row title: `400 21px/1.2 display` (`22px` ≥720px).
|
- Row title: `400 21px/1.2 display` (`22px` ≥720px).
|
||||||
- Tab: `400 17px display` (`18px` ≥720px), active gets `border-bottom: 2px` in
|
- Tab: `400 17px display` (`18px` ≥720px), active gets `border-bottom: 2px` in
|
||||||
`--paper` (`--ember` for Updated) plus `margin-bottom: -1px` so it lands on
|
`--paper` (`--ember` for Updated) plus `margin-bottom: -1px` so it lands on
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ Guidance for Claude Code working under `userscript/`. See root `CLAUDE.md` for t
|
|||||||
### Userscript structure (single IIFE, `manga-bookmark.user.js`)
|
### Userscript structure (single IIFE, `manga-bookmark.user.js`)
|
||||||
|
|
||||||
1. **Site adapters** — one per host, `detect(location, document)` return page `type` + IDs. Identify type/IDs from **URL regex** (most stable); pull `title`/`cover` from **`og:title`/`og:image` meta tags**, not CSS classes.
|
1. **Site adapters** — one per host, `detect(location, document)` return page `type` + IDs. Identify type/IDs from **URL regex** (most stable); pull `title`/`cover` from **`og:title`/`og:image` meta tags**, not CSS classes.
|
||||||
2. **API client** — `apiGet/apiPut/apiDelete` with bearer header; `localStorage` key `mangabm:cache` for instant render + offline fallback.
|
2. **API client** — `apiGet/apiPut/apiDelete` with bearer header; `localStorage` key `bmgr:manga:cache` for instant render + offline fallback.
|
||||||
3. **Progress logic** — auto-upsert `last_chapter` only when `chapterNum >= stored last_chapter_num` (re-reading old chapters must not regress progress; unparseable -> set current). Manual panel override forces any value.
|
3. **Progress logic** — auto-upsert `last_chapter` only when `chapterNum >= stored last_chapter_num` (re-reading old chapters must not regress progress; unparseable -> set current). Manual panel override forces any value.
|
||||||
4. **Retry queue** — every write go through `pushBookmark`/`pushDelete`, so
|
4. **Retry queue** — every write go through `pushBookmark`/`pushDelete`, so
|
||||||
failed mutation park in `localStorage` (`mangabm:queue`) and replayed on
|
failed mutation park in `localStorage` (`bmgr:manga:queue`) and replayed on
|
||||||
next navigation, reconnect, or `refresh()`. Entries are markers
|
next navigation, reconnect, or `refresh()`. Entries are markers
|
||||||
(`{key, op, sendStatus, attempts}`), never payloads — body read from
|
(`{key, op, sendStatus, attempts}`), never payloads — body read from
|
||||||
cache at send time, so one entry per key give ordering and coalescing for
|
cache at send time, so one entry per key give ordering and coalescing for
|
||||||
|
|||||||
Reference in New Issue
Block a user