From ad46061db75ec56959a9410e5d7695bc3dc1081a Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:13:01 -0600 Subject: [PATCH] chore: install kepubify into Docker image at build time - download correct arch binary from projectx-tools during image build - use system binary at /usr/local/bin/kepubify when available, fall back to runtime download for local dev --- Dockerfile | 13 ++++++++++++- .../services/kepubify-binary.service.test.ts | 13 ++++++++++++- .../kobo/services/kepubify-binary.service.ts | 18 +++++++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7e0ed62a4..f92bb74bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,8 +40,19 @@ RUN mkdir -p /deploy/migrations && cp -r /app/server/src/db/migrations/. /deploy FROM ${NODE_IMAGE} AS runtime WORKDIR /app +ARG TARGETARCH RUN apk upgrade --no-cache && \ - apk add --no-cache poppler-utils su-exec && \ + apk add --no-cache poppler-utils su-exec curl && \ + case "$TARGETARCH" in \ + amd64) KEPUBIFY_BIN="kepubify-linux-64bit" ;; \ + arm64) KEPUBIFY_BIN="kepubify-linux-arm64" ;; \ + arm) KEPUBIFY_BIN="kepubify-linux-arm" ;; \ + 386) KEPUBIFY_BIN="kepubify-linux-32bit" ;; \ + esac && \ + curl -fsSL "https://github.com/neonsolstice/projectx-tools/raw/main/kepubify/${KEPUBIFY_BIN}" \ + -o /usr/local/bin/kepubify && \ + chmod +x /usr/local/bin/kepubify && \ + apk del curl && \ rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx ENV NODE_ENV=production diff --git a/server/src/modules/kobo/services/kepubify-binary.service.test.ts b/server/src/modules/kobo/services/kepubify-binary.service.test.ts index 11571da95..c1f83e22a 100644 --- a/server/src/modules/kobo/services/kepubify-binary.service.test.ts +++ b/server/src/modules/kobo/services/kepubify-binary.service.test.ts @@ -48,8 +48,19 @@ describe('KepubifyBinaryService', () => { expect(() => (service as any).detectBinaryName()).toThrow('Unsupported platform'); }); - it('caches computed binary path after first resolution', async () => { + it('returns system binary path when kepubify is already installed', async () => { const service = new KepubifyBinaryService(config as never); + statMock.mockResolvedValue({} as never); + + await expect(service.getBinaryPath()).resolves.toBe('/usr/local/bin/kepubify'); + await expect(service.getBinaryPath()).resolves.toBe('/usr/local/bin/kepubify'); + + expect(statMock).toHaveBeenCalledTimes(1); + }); + + it('caches computed binary path after first resolution when system binary absent', async () => { + const service = new KepubifyBinaryService(config as never); + statMock.mockRejectedValue(new Error('missing')); const detectSpy = vi.spyOn(service as any, 'detectBinaryName').mockReturnValue('kepubify-linux-64bit'); const ensureSpy = vi.spyOn(service as any, 'ensureBinary').mockResolvedValue(undefined); diff --git a/server/src/modules/kobo/services/kepubify-binary.service.ts b/server/src/modules/kobo/services/kepubify-binary.service.ts index 3651d36bb..9e47dc5aa 100644 --- a/server/src/modules/kobo/services/kepubify-binary.service.ts +++ b/server/src/modules/kobo/services/kepubify-binary.service.ts @@ -4,7 +4,8 @@ import { join } from 'path'; import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -const KEPUBIFY_BASE_URL = 'https://github.com/booklore-app/booklore-tools/raw/main/kepubify/'; +const KEPUBIFY_BASE_URL = 'https://github.com/neonsolstice/projectx-tools/raw/main/kepubify/'; +const SYSTEM_BINARY_PATH = '/usr/local/bin/kepubify'; @Injectable() export class KepubifyBinaryService { @@ -18,6 +19,12 @@ export class KepubifyBinaryService { async getBinaryPath(): Promise { if (this.cachedBinaryPath) return this.cachedBinaryPath; + + if (await this.exists(SYSTEM_BINARY_PATH)) { + this.cachedBinaryPath = SYSTEM_BINARY_PATH; + return SYSTEM_BINARY_PATH; + } + const binaryName = this.detectBinaryName(); const toolsDir = join(this.appDataPath, '.tools', 'kepubify'); const binaryPath = join(toolsDir, binaryName); @@ -26,6 +33,15 @@ export class KepubifyBinaryService { return binaryPath; } + private async exists(path: string): Promise { + try { + await stat(path); + return true; + } catch { + return false; + } + } + private detectBinaryName(): string { const platform = process.platform; const arch = process.arch;