|
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (rust) (push) Waiting to run
PR checks / rust_lint (push) Waiting to run
PR checks / build_web_app (push) Waiting to run
PR checks / test_web_app (push) Waiting to run
PR checks / build_tauri_app (push) Waiting to run
Deploy to vercel on merge / build_and_deploy (push) Waiting to run
* feat(sync): cross-device dictionary sync
Custom MDict / StarDict / DICT / SLOB dictionaries now sync across
signed-in devices via the replica layer.
- Store mutations publish replica rows with field-level LWW + tombstones.
- Re-importing the same content (renamed or after delete) preserves the
user's label and reincarnates the server row instead of duplicating.
- Manifest commits after binary upload so other devices never see a row
whose binaries aren't on cloud storage yet.
- Pull-side orchestrator creates a placeholder dict, queues the binaries
via TransferManager, and clears the unavailable flag on completion.
- Toast copy branches by transfer kind so dict uploads don't read
"Book uploaded".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sync): boot pull and binary download path
- Defer the boot pull until TransferManager is initialized so download
enqueues aren't dropped.
- Auto-persist the local dict store after applyRemoteDictionary; otherwise
the next loadCustomDictionaries wipes the in-memory rows.
- Boot pull passes since=null so a device whose cursor advanced past
unpersisted rows can still recover.
- Skip pulling when not authenticated instead of logging
"SyncError: Not authenticated" on every boot of a signed-out device.
- downloadReplicaFile resolves the destination against the kind's base
dir; binaries previously landed at the literal lfp and openFile then
failed with "File not found".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(sync): per-page useReplicaPull hook
Lifts the boot-time pull out of EnvContext into a hook each page mounts
for the kinds it needs: useReplicaPull({ kinds: ['dictionary'] }).
Library page and the shared Reader component opt in. The hook fires 10s
after page load (so feature mounts hydrate first), dedups per-kind
across navigation, and releases the slot on failure so a later mount
can retry. Future kinds plug into the hook's per-kind switch.
Also closes two refresh-loop bugs:
- Hydrate the dict store from settings BEFORE the apply loop, so the
auto-persist doesn't clobber persisted rows that the in-memory store
hadn't yet read. Library-page refresh was the visible victim.
- Skip the download queue when every manifest file is already on disk
under the resolved bundle dir. Refreshing is a no-op; partial-
download recovery still queues because some files would be missing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| volumes | ||
| .env.example | ||
| compose.yaml | ||
| README.md | ||
Self-Hosting with Docker/Podman with Compose
Stack
| service | Image | Description |
|---|---|---|
| client | from ../Dockerfile |
readest frontend |
| db | supabase/postgres |
psql db with supabase extensions |
| kong | kong:2.8.1 |
api gateway routing requests to supabase services |
| auth | supabase/gotrue:v2.185.0 |
auth service (email, JWT) |
| rest | postgrest/postgrest:v14.3 |
psql rest api |
| minio | minio/minio |
s3 storage |
| minio-setup | minio/mc |
helper container to create s3 buckets |
Exposed ports
| Port | Service |
|---|---|
3000 |
readest |
7000 |
kong API gateway |
9000 |
MinIO S3 API |
9001 |
MinIO console UI |
Running with Docker/Podman Compose
1. setup .env
cp docker/.env.example docker/.env
update docker/.env:
- update
POSTGRES_PASSWORDto a strong password (32+ chars) - update
JWT_SECRETto a random secret (32+ chars) - regenerate
ANON_KEYandSERVICE_ROLE_KEYas HS256 JWTs signed with yourJWT_SECRET(use jwt.io or a similar tool):ANON_KEYpayload:{"role": "anon"}SERVICE_ROLE_KEYpayload:{"role": "service_role"}
- set
MINIO_ROOT_PASSWORDto a strong password
2. Start the Stack
run from the docker/ directory:
cd docker
docker compose up --build -d
the client image is built locally on first run. subsequent starts reuse the cached image.
3. Access
- Readest app:
http://localhost:3000 - MinIO console:
http://localhost:9001(login withMINIO_ROOT_USER/MINIO_ROOT_PASSWORD)
Hot Reload (development)
to develop using the compose stack, set the build target on client to development-stage, which'll runs the next.js dev server. to enable hot reload, uncomment the volumes block in the client service in compose.yaml:
volumes:
- ../:/app
- /app/node_modules
- /app/apps/readest-app/node_modules
- /app/apps/readest-app/public/vendor
- /app/apps/readest-app/.next
- /app/packages/foliate-js/node_modules
the first mount overlays your local repo into the container. the remaining anonymous volumes shadow the directories that were pre-built inside the image, so the container's installed deps and vendor assets are used instead of what's on your host.
Stop the Stack
cd docker
docker compose down
to also remove volumes (database and storage data):
cd docker
docker compose down -v
Building the Dockerfile standalone
the Dockerfile requires Build args for the next.js public env vars (they are inlined at build time)
docker build \
--target production-stage \
--build-arg NEXT_PUBLIC_SUPABASE_URL=http://localhost:7000 \
--build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key> \
--build-arg NEXT_PUBLIC_APP_PLATFORM=web \
--build-arg NEXT_PUBLIC_API_BASE_URL=http://localhost:3000 \
--build-arg NEXT_PUBLIC_OBJECT_STORAGE_TYPE=s3 \
--build-arg NEXT_PUBLIC_STORAGE_FIXED_QUOTA=1073741824 \
--build-arg NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA=50000 \
-t readest-client \
.
run the built image:
docker run -p 3000:3000 \
-e SUPABASE_URL=http://kong:8000 \
-e SUPABASE_ANON_KEY=<anon-key> \
-e SUPABASE_ADMIN_KEY=<service-role-key> \
-e S3_ENDPOINT=http://localhost:9000 \
-e S3_REGION=us-east-1 \
-e S3_BUCKET_NAME=readest-files \
-e S3_ACCESS_KEY_ID=<minio-user> \
-e S3_SECRET_ACCESS_KEY=<minio-password> \
readest-client