diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml new file mode 100644 index 000000000..4851a8825 --- /dev/null +++ b/.github/workflows/build-container.yml @@ -0,0 +1,65 @@ +name: Build Listenarr container + +on: + workflow_dispatch: + + push: + branches: + - feature/symlink-import + tags: + - "v*" + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io + +jobs: + build: + name: Build and publish container + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Determine image name + id: image + shell: bash + run: | + IMAGE_NAME="${GITHUB_REPOSITORY,,}" + echo "name=${REGISTRY}/${IMAGE_NAME}" >> "$GITHUB_OUTPUT" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image metadata + id: metadata + uses: docker/metadata-action@v5 + with: + images: ${{ steps.image.outputs.name }} + tags: | + type=raw,value=symlink-latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=sha- + + - name: Build and publish image + uses: docker/build-push-action@v7 + with: + context: . + platforms: linux/amd64 + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/README.md b/README.md index 0811c7c1f..70e193ef5 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,26 @@ Supported download clients: - Automatic metadata fetching - Library management options +#### Completed File Action + +Choose how completed downloads are placed into the library (Settings → File Management → *Completed File Action*): + +- **Move** – move the file into the library and remove it from the client's folder. +- **Copy** – copy the file into the library and leave the original in place. +- **Hardlink/Copy** – create a hardlink into the library (falls back to a copy when source and destination are on different filesystems). +- **Symbolic Link** (`symlink`) – create a symbolic link in the library instead of copying the file. The source content is never read or copied and the original source is left untouched. This is primarily useful with virtual filesystems such as **NZBDav**, **rclone** and other WebDAV/FUSE mounts, where a hardlink is impossible (cross-filesystem) and a copy would download the entire file. + + When the source is itself a symlink, Listenarr reads its target and links the library entry **directly** to the final target, avoiding a symlink chain. Both absolute and relative link targets are supported (relative targets are resolved against the source symlink's directory). + + > **Important:** the source and destination paths must stay reachable for the link to work. Because absolute link targets are preserved as-is, the underlying path (e.g. the NZBDav/rclone mount) must be mounted at the **same absolute path** in every container that reads the library — both Listenarr and your audiobook player (e.g. Audiobookshelf). The link will break whenever the source path (mount) is unavailable. + > + > Example — both containers must expose identical absolute paths: + > + > ```text + > Listenarr: /mnt/nzbdav + /data/media/audiobooks + > Audiobookshelf: /mnt/nzbdav + /data/media/audiobooks + > ``` + ## API Endpoints ### Search diff --git a/fe/src/components/domain/audiobook/LibraryImportFooter.vue b/fe/src/components/domain/audiobook/LibraryImportFooter.vue index 4b296227b..6d9e38c91 100644 --- a/fe/src/components/domain/audiobook/LibraryImportFooter.vue +++ b/fe/src/components/domain/audiobook/LibraryImportFooter.vue @@ -31,6 +31,7 @@ +