diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8399e07 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +services: + readmeabook: + image: ghcr.io/kikootwo/readmeabook:latest +# build: +# context: . +# dockerfile: dockerfile.unified + container_name: readmeabook + restart: unless-stopped + ports: + - "3030:3030" + volumes: + # Application config and cache + - ./config:/app/config + - ./cache:/app/cache + + # Downloads and media directories + - ./downloads:/downloads + - ./media:/media + + # PostgreSQL data persistence + - ./pgdata:/var/lib/postgresql/data + + # Redis data persistence + - ./redis:/var/lib/redis + + environment: + # ======================================================================== + # RECOMMENDED: User/Group ID Mapping (Hybrid Approach) + # ======================================================================== + # Set these to match your host user for proper file ownership + # Run 'id' on your host to find your UID and GID + # + # How it works: + # - postgres user: Keeps UID 103 (PostgreSQL requirement), uses your PGID + # - redis/node: Fully remapped to your PUID:PGID + # + # File ownership on host: + # - PostgreSQL data (/var/lib/postgresql/data): UID 103, GID + # - Everything else (/downloads, /media, /config): : + # + # For LXC: You only need to passthrough/map container UID 103 + # See documentation/deployment/unified.md for LXC examples + # + PUID: 1000 + PGID: 1000 + + # ======================================================================== + # OPTIONAL: Secrets (auto-generated on first run if not provided) + # ======================================================================== + # Uncomment and set these if you want to use custom secrets: + # JWT_SECRET: "your-custom-jwt-secret-here" + # JWT_REFRESH_SECRET: "your-custom-jwt-refresh-secret-here" + # CONFIG_ENCRYPTION_KEY: "your-custom-encryption-key-here" + # POSTGRES_PASSWORD: "your-custom-postgres-password-here" + + # ======================================================================== + # OPTIONAL: Application Configuration + # ======================================================================== + # Only set these if you need non-default values: + # POSTGRES_USER: "readmeabook" + # POSTGRES_DB: "readmeabook" + # PLEX_CLIENT_IDENTIFIER: "readmeabook-custom-id" + # PLEX_PRODUCT_NAME: "ReadMeABook" + # LOG_LEVEL: "info" + + # ======================================================================== + # IMPORTANT: Public URL Configuration (Required for OAuth) + # ======================================================================== + # Set this to your public URL for OAuth callbacks (Plex/OIDC authentication) + # Format: https://your-domain.com (no trailing slash) + # REQUIRED if accessing from outside localhost or using OIDC/Plex OAuth + # See: documentation/backend/services/environment.md + # PUBLIC_URL: "https://readmeabook.yourdomain.com" + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3030/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s diff --git a/src/lib/utils/files-hash.ts b/src/lib/utils/files-hash.ts index 8d72413..072ee48 100644 --- a/src/lib/utils/files-hash.ts +++ b/src/lib/utils/files-hash.ts @@ -44,7 +44,11 @@ export function generateFilesHash(filePaths: string[]): string { // Extract basenames and filter to audio files only const audioBasenames = filePaths - .map((filePath) => path.basename(filePath)) + .map((filePath) => { + // Normalize path separators to forward slashes for cross-platform consistency + const normalizedPath = filePath.replace(/\\/g, '/'); + return path.posix.basename(normalizedPath); + }) .filter((basename) => { const ext = path.extname(basename).toLowerCase(); return AUDIO_EXTENSIONS.includes(ext);