From 885ef19cf410d24faae9e9640964d71192c8ddb7 Mon Sep 17 00:00:00 2001 From: Volodymyr Dyptan Date: Sat, 7 Mar 2026 11:29:34 +0100 Subject: [PATCH 1/2] Add python http server to run beets cmd --- .devcontainer/devcontainer.json | 17 + .dockerignore | 6 + .gitattributes | 17 + .github/workflows/BuildImage.yml | 5 +- .gitignore | 43 ++ Dockerfile | 8 + README.md | 401 +++++++----------- blacklist.txt | 2 - mod-list.yml | 220 ---------- .../dependencies.d/init-mods | 0 .../s6-rc.d/init-mod-beets-httpshell/run | 5 + .../s6-rc.d/init-mod-beets-httpshell/type | 1 + .../s6-rc.d/init-mod-beets-httpshell/up | 1 + .../dependencies.d/init-mod-beets-httpshell | 0 .../dependencies.d/init-services | 0 .../s6-rc.d/svc-mod-beets-httpshell/run | 3 + .../s6-rc.d/svc-mod-beets-httpshell/type | 1 + .../user/contents.d/init-mod-beets-httpshell | 0 .../user/contents.d/svc-mod-beets-httpshell | 0 root/usr/local/bin/beets-httpshell.py | 188 ++++++++ 20 files changed, 456 insertions(+), 462 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dockerfile delete mode 100644 blacklist.txt delete mode 100644 mod-list.yml create mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/dependencies.d/init-mods create mode 100755 root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/run create mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/type create mode 100644 root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/up create mode 100644 root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mod-beets-httpshell create mode 100644 root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/dependencies.d/init-services create mode 100755 root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/run create mode 100644 root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/type create mode 100644 root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-beets-httpshell create mode 100644 root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-beets-httpshell create mode 100755 root/usr/local/bin/beets-httpshell.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..778fe8b77 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "name": "beets-httpshell", + "image": "mcr.microsoft.com/devcontainers/python:3", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance" + ], + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.analysis.typeCheckingMode": "basic" + } + } + }, + "forwardPorts": [5555] +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..df5e3103a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.gitignore +.github +.gitattributes +READMETEMPLATE.md +README.md \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..198a7c455 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain \ No newline at end of file diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index 0a47318a4..d87e583ad 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -5,7 +5,8 @@ on: env: GITHUB_REPO: "linuxserver/docker-mods" - ENDPOINT: "linuxserver/mods" + ENDPOINT: "dyptan-io/docker-mods" + BRANCH: "main" BASEIMAGE: "pr" MODNAME: "build" MULTI_ARCH: "true" @@ -33,7 +34,7 @@ jobs: MOD_VERSION: ${{ steps.outputs.outputs.MOD_VERSION }} build: - if: ${{ github.base_ref != 'master' }} + if: ${{ github.base_ref != 'main' }} uses: linuxserver/github-workflows/.github/workflows/docker-mod-builder.yml@v1 needs: set-vars secrets: diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3a877a82f --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..8e091a762 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +# syntax=docker/dockerfile:1 + +FROM scratch + +LABEL maintainer="dyptan-io" + +# copy local files +COPY root/ / \ No newline at end of file diff --git a/README.md b/README.md index 83357b79b..8dbb23788 100644 --- a/README.md +++ b/README.md @@ -1,308 +1,233 @@ -# Intro +# beets-httpshell -The purpose of the repository is to provide examples and guidance in creating and storing a user consumable modification layer for the Library of Linuxserver.io Containers. -At its core a Docker Mod is a tarball of files stored on Dockerhub and/or GitHub Container Registry that is downloaded and extracted on container boot before any init logic is run. -This allows: +A [LinuxServer.io Docker Mod](https://github.com/linuxserver/docker-mods) for the [beets](https://github.com/linuxserver/docker-beets) container that adds a lightweight HTTP API to execute `beet` CLI commands remotely. -* Developers and community users to modify base containers to suit their needs without the need to maintain a fork of the main docker repository -* Mods to be shared with the Linuxserver.io userbase as individual independent projects with their own support channels and development ideologies -* Zero cost hosting and build pipelines for these modifications leveraging GitHub Container Registry and Dockerhub -* Full custom configuration management layers for hooking containers into each other using environment variables contained in a compose file +The mod runs a Python 3 HTTP server (no extra dependencies) that maps URL paths to beet subcommands. Any beet command can be invoked — there is no hardcoded command list. -It is important to note to end users of this system that there are not only extreme security implications to consuming files from sources outside of our control, but by leveraging community Mods you essentially lose direct support from the core LinuxServer team. Our first and foremost troubleshooting step will be to remove the `DOCKER_MODS` environment variable when running into issues and replace the container with a clean LSIO one. +> **⚠️ Security Warning:** The HTTP API has no authentication or authorization. Any client that can reach the server can execute arbitrary beet commands. It is your responsibility to ensure the API is not exposed to untrusted networks — use firewall rules, Docker network isolation, or a reverse proxy with authentication to restrict access. -Again, when pulling in logic from external sources practice caution and trust the sources/community you get them from. +## Installation -## LinuxServer.io Hosted Mods +Add the mod to your beets container using the `DOCKER_MODS` environment variable. -We host and publish official Mods at the [linuxserver/mods](https://github.com/orgs/linuxserver/packages/container/mods/versions) endpoint as separate tags. Each tag is in the format of `-` for the latest versions, and `--` for the specific versions. - -Here's a list of the official Mods we host: [https://mods.linuxserver.io/](https://mods.linuxserver.io/) - -## Using a Docker Mod - -Before consuming a Docker Mod ensure that the source code for it is publicly posted along with its build pipeline pushing to Dockerhub. - -Consumption of a Docker Mod is intended to be as user friendly as possible and can be achieved with the following environment variables being passed to the container: - -* DOCKER_MODS- This can be a single endpoint `user/endpoint:tag` or an array of endpoints separated by `|` `user/endpoint:tag|user2/endpoint2:tag` -* RUN_BANNED_MODS- If this is set to any value you will bypass our centralized filter of banned Dockerhub users and run Mods regardless of a ban - -Full example: - -docker run +### docker run ```bash -docker create \ - --name=nzbget \ - -e DOCKER_MODS=lscr.io/linuxserver/mods:universal-tshoot \ +docker run \ + --name=beets \ + -e DOCKER_MODS=ghcr.io/linuxserver/mods:beets-httpshell \ -e PUID=1000 \ -e PGID=1000 \ -e TZ=Europe/London \ - -p 6789:6789 \ - -v /path/to/nzbget/data:/config \ + -p 8337:8337 \ + -p 5555:5555 \ + -v /path/to/config:/config \ + -v /path/to/music:/music \ -v /path/to/downloads:/downloads \ --restart unless-stopped \ - lscr.io/linuxserver/nzbget + lscr.io/linuxserver/beets:latest ``` - docker compose +### docker compose ```yaml --- services: - nzbget: - image: lscr.io/linuxserver/nzbget:latest - container_name: nzbget + beets: + image: lscr.io/linuxserver/beets:latest + container_name: beets environment: - - DOCKER_MODS=lscr.io/linuxserver/mods:universal-tshoot - - PUID=1000 - - PGID=1000 - - TZ=Europe/London + DOCKER_MODS: ghcr.io/linuxserver/mods:beets-httpshell + PUID: 1000 + PGID: 1000 + TZ: Europe/London + HTTPSHELL_PORT: 5555 volumes: - - /path/to/nzbget/data:/config - - /path/to/downloads:/downloads #optional + - /path/to/config:/config + - /path/to/music:/music + - /path/to/downloads:/downloads ports: - - 6789:6789 + - 8337:8337 + - 5555:5555 restart: unless-stopped ``` -This will spin up an nzbget container and apply the custom logic found in the following repository: - -[https://github.com/linuxserver/docker-mods/tree/universal-tshoot](https://github.com/linuxserver/docker-mods/tree/universal-tshoot) +## Environment Variables -This mod installs some basic troubleshooting tools such as dig, netstat, nslookup, etc. +| Variable | Default | Description | +|---|---|---| +| `BEET_CMD` | `/lsiopy/bin/beet` | Path to the `beet` binary | +| `BEET_CONFIG` | `/config/config.yaml` | Path to the beets config file | +| `HTTPSHELL_PORT` | `5555` | Port the HTTP server listens on | +| `HTTPSHELL_BLOCKING_TIMEOUT` | `30` | Seconds to wait for the lock in `blocking` mode before the job is queued | -## Creating and maintaining a Docker Mod +## API Usage -**All of the example files referenced in this section are available in the [template](https://github.com/linuxserver/docker-mods/tree/template) branch of this repo.** +### Execute a command -We will always recommend to our users consuming Mods that they leverage ones from active community members or projects so transparency is key here. We understand that image layers can be pushed on the back end behind these pipelines, but every little bit helps. In this repository we will be going over two basic methods of making a Mod along with an example of the GitHub Actions build logic to get this into a Dockerhub and/or GitHub Container Registry endpoint. Though we are not officially endorsing GitHub Actions here it is built in to GitHub repositories and forks making it very easy to get started. If you prefer others feel free to use them as long as build jobs are transparent. - -> **Note** -> One of the core ideas to remember when creating a Mod is that it can only contain a **single image layer**, the examples below will show you how to add files standardly and how to run complex logic to assemble the files in a build layer to copy them over into this single layer. - -### Mod Types - -We now only support s6 v3 mods. All currently supported Linuxserver base images are using s6 v3, however, older pinned images, forked versions, etc. may still be using v2. New mods will not work with older s6 v2 based images. - -### Docker Mod Simple - just add scripts - -In this repository you will find the `Dockerfile` containing: - -```Dockerfile -FROM scratch - -# copy local files -COPY root/ / ``` +POST /?mode=blocking +Content-Type: application/json -For most users this will suffice and anything in the root/ folder of the repository will be added to the end users Docker container / path. - -#### New (v3) mods - -The most common paths to leverage for Linuxserver images are as follows. Assuming a mod name of `universal-mymod`: - -```text -. -└── root - ├── defaults -- Any default config files you need to copy as part of the mod can be placed here - └── etc - └── s6-overlay - └── s6-rc.d - ├── init-mods-end - │ └── dependencies.d - │ └── init-mod-universal-mymod -- If your mod does not need to install packages it should be a dependency of init-mods-end (empty file) - ├── init-mods-package-install - │ └── dependencies.d - │ └── init-mod-universal-mymod -- If your mod needs to install packages it should be a dependency of init-mods-package-install (empty file) - ├── init-mod-universal-mymod - │ ├── dependencies.d - │ │ └── init-mods -- Tells our init logic that this depends on the `init-mods` step (empty file) - │ ├── run -- This is the init logic script that runs before the services in the container. Use `chmod +x` to set proper permissions. - │ ├── type -- This should contain the string `oneshot`. - │ └── up -- This should contain the absolute path to `run` e.g. `/etc/s6-overlay/s6-rc.d/init-mod-universal-mymod/run`. - ├── svc-mod-universal-mymod - │ ├── dependencies.d - │ │ └── init-services -- Tells our init logic that this depends on the `init-services` step (empty file) - │ ├── run -- This is the script that runs in the foreground for persistent services. Use `chmod +x` to set proper permissions. - │ └── type -- This should contain the string `longrun`. - └── user - └── contents.d - ├── init-mod-universal-mymod -- Tells our init logic that we need this directory (empty file) - └── svc-mod-universal-mymod -- Tells our init logic that we need this directory (empty file) +["arg1", "arg2", ...] ``` -> **Note** -> For `oneshot` scripts you can alternatively omit the `run` file entirely and use the [execlineb](https://skarnet.org/software/execline/execlineb.html) syntax in `up` if your requirements are simple enough. - -#### Installing Packages +The URL path is the beet subcommand. The optional `?mode=` query parameter controls execution mode (`blocking`, `async`, or `queued` — defaults to `blocking`). The JSON body is an array of string arguments. An empty body or `[]` means no arguments. -v3 mods make use of a single package install process for all mods to minimise the amount of calls to external endpoints and speed up the mod init process. If you need to install repo packages you should append them to `/mod-repo-packages-to-install.list` for repo packages or `/mod-pip-packages-to-install.list` for pip packages and the mod handler will install them for you. Make sure to handle both Ubuntu and Alpine package names if your mod needs to support both e.g. +**Response** (200 OK): -```bash -#!/usr/bin/with-contenv bash - -## Ubuntu -if [ -f /usr/bin/apt ]; then - echo "\ - dnsutils \ - net-tools \ - iputils-ping \ - traceroute" >> /mod-repo-packages-to-install.list - -fi -# Alpine -if [ -f /sbin/apk ]; then - echo "\ - bind-tools \ - net-tools" >> /mod-repo-packages-to-install.list -fi +```json +{ + "command": "stats", + "args": [], + "exit_code": 0, + "stdout": "Tracks: 1234\nTotal time: 3.2 days\n...", + "stderr": "" +} ``` -If your mod needs to take additional config steps *after* the packages have been installed, add a second `oneshot` script and make it depend on `init-mods-package-install`, add it as a dependency of `init-mods-end`, and add it to the content bundle e.g. +### Health check -```text -. -└── root - └── etc - └── s6-overlay - └── s6-rc.d - ├── init-mods-end - │ └── dependencies.d - │ └── init-mod-universal-mymod-postinstall - ├── init-mod-universal-mymod-postinstall - │ ├── dependencies.d - │ │ └── init-mods-package-install - │ ├── run - │ ├── type - │ └── up - └── user - └── contents.d - └── init-mod-universal-mymod-postinstall ``` - -Services will always run last, controlled by their dependency on `init-services`. - -### Docker Mod Complex - Sky is the limit - -In this repository you will find the `Dockerfile.complex` containing: - -```Dockerfile -## Buildstage ## -FROM ghcr.io/linuxserver/baseimage-alpine:3.20 as buildstage - -RUN \ - echo "**** install packages ****" && \ - apk add --no-cache \ - curl && \ - echo "**** grab rclone ****" && \ - mkdir -p /root-layer && \ - curl -o \ - /root-layer/rclone.deb -L \ - "https://downloads.rclone.org/v1.47.0/rclone-v1.47.0-linux-amd64.deb" - -# copy local files -COPY root/ /root-layer/ - -## Single layer deployed image ## -FROM scratch - -# Add files from buildstage -COPY --from=buildstage /root-layer/ / +GET /health ``` -Here we are leveraging a multi stage DockerFile to run custom logic and pull down an rclone deb from the Internet to include in our image layer for distribution. Any amount of logic can be run in this build stage or even multiple build stages as long as the files in the end are combined into a single folder for the COPY command in the final output. - -## Getting a Mod to Dockerhub - -To publish a Mod to DockerHub you will need the following accounts: - -* Github- [https://github.com/join](https://github.com/join) -* DockerHub- [https://hub.docker.com/signup](https://hub.docker.com/signup) - -We recommend using this repository as a template for your first Mod, so in this section we assume the code is finished and we will only concentrate on plugging into GitHub Actions/Dockerhub. - -The only code change you need to make to the build logic file `.github/workflows/BuildImage.yml` will be to modify the ENDPOINT to your own image: +Returns `200 OK` with server status: -```yaml - ENDPOINT: "user/endpoint" - BRANCH: "master" +```json +{ + "status": "ok", + "default_mode": "blocking", + "queue_size": 0 +} ``` -User is your Dockerhub user and endpoint is your own custom name (typically the name of the repository where your mod is). You do not need to create this endpoint beforehand, the build logic will push it and create it on first run. - -Head over to `https://github.com/user/endpoint/settings/secrets` and click on `New secret` - -Add `DOCKERUSER` (your DockerHub username) and `DOCKERPASS` (your DockerHub password or token). - -You can create a token by visiting [https://hub.docker.com/settings/security](https://hub.docker.com/settings/security) +### Examples -GitHub Actions will trigger a build off of your repo when you commit. The image will be pushed to Dockerhub on success. This Dockerhub endpoint is the Mod variable you can use to customize your container now. - -## Getting a Mod to GitHub Container Registry +```bash +# Get library stats (default blocking mode) +curl -X POST http://localhost:5555/stats + +# List all tracks by an artist +curl -X POST http://localhost:5555/list \ + -H "Content-Type: application/json" \ + -d '["artist:Radiohead"]' + +# Import music asynchronously (returns result when done, runs in parallel with other async requests) +curl -X POST 'http://localhost:5555/import?mode=async' \ + -H "Content-Type: application/json" \ + -d '["/downloads/music", "--quiet", "--incremental"]' + +# Queue an import (returns 202 immediately, runs in background) +curl -X POST 'http://localhost:5555/import?mode=queued' \ + -H "Content-Type: application/json" \ + -d '["/downloads/music"]' + +# Update the library +curl -X POST http://localhost:5555/update + +# Get beets configuration +curl -X POST http://localhost:5555/config + +# Remove tracks matching a query (force, delete files) +curl -X POST http://localhost:5555/remove \ + -H "Content-Type: application/json" \ + -d '["artist:test", "-d", "-f"]' + +# Move items to a new directory +curl -X POST http://localhost:5555/move \ + -H "Content-Type: application/json" \ + -d '["artist:Radiohead", "-d", "/music/favorites"]' + +# Health check +curl http://localhost:5555/health +``` -To publish a Mod to GitHub Container Registry you will need the following accounts: +## Execution Modes -* Github- [https://github.com/join](https://github.com/join) +The execution mode is controlled per-request via the `?mode=` query parameter. If omitted, defaults to `blocking`. -We recommend using this repository as a template for your first Mod, so in this section we assume the code is finished and we will only concentrate on plugging into GitHub Actions/GitHub Container Registry. +### `blocking` (default) -The only code change you need to make to the build logic file `.github/workflows/BuildImage.yml` will be to modify the ENDPOINT to your own image: +Each request waits for a global lock. If the lock is acquired within `HTTPSHELL_BLOCKING_TIMEOUT` seconds, the command runs and the result is returned (200). If the timeout expires, the job is queued and a 202 is returned instead. This ensures commands run one at a time. -```yaml - ENDPOINT: "user/endpoint" - BRANCH: "master" +``` +Request 1 ──▶ [acquires lock, runs command] ──▶ 200 response +Request 2 ──▶ [waits for lock... acquired] ──▶ 200 response +Request 3 ──▶ [waits for lock... timeout] ──▶ 202 (queued) ``` -`user` is your GitHub user and `endpoint` is your own custom name (typically the name of the repository where your mod is). You do not need to create this endpoint beforehand, the build logic will push it and create it on first run. - -Head over to `https://github.com///settings/secrets` and click on `New secret` +### `async` -Add `CR_USER` (your GitHub username) and `CR_PAT` (a personal access token with `read:packages` and `write:packages` scopes). +Each request runs its command immediately in its own thread. Multiple commands execute in parallel. The response is returned when the command finishes. -You can create a personal access token by visiting [https://github.com/settings/tokens](https://github.com/settings/tokens) +``` +Request 1 ──▶ [runs command] ──▶ 200 response +Request 2 ──▶ [runs command] ──▶ 200 response (runs in parallel) +``` -GitHub Actions will trigger a build off of your repo when you commit. The image will be pushed to GitHub Container Registry on success. This GitHub Container Registry endpoint is the Mod variable you can use to customize your container now. +### `queued` -## Submitting a PR for a Mod to be added to the official LinuxServer.io repo +Every request returns `202 Accepted` immediately. Commands are placed in a FIFO queue and executed one at a time by a background worker. Useful for commands that shouldn't overlap (e.g., `import`). -* Fork this repo, checkout the `template` branch. -* Edit the `Dockerfile` for the mod. `Dockerfile.complex` is only an example and included for reference; it should be deleted when done. -* Ensure you set the `maintainer` label to your GitHub username, this allows us to @ you if there are breaking changes that affect or will affect your mod in the future. -* Inspect the `root` folder contents. Edit, add and remove as necessary. -* After all init scripts and services are created, run `find ./ -path "./.git" -prune -o ( -name "run" -o -name "finish" -o -name "check" ) -not -perm -u=x,g=x,o=x -print -exec chmod +x {} +` to fix permissions. -* Edit the readme with pertinent info. -* Finally edit the `.github/workflows/BuildImage.yml`. Customize the vars for `BASEIMAGE` and `MODNAME`. Set the versioning logic and `MULTI_ARCH` if needed. -* Ask the team to create a new branch named `-` in this repo. Baseimage should be the name of the image the mod will be applied to. The new branch will be based on the [template branch](https://github.com/linuxserver/docker-mods/tree/template). -* Submit PR against the branch created by the team. -* Make sure that the commits in the PR are squashed. -* Also make sure that the commit and PR titles are in the format of `: `. Detailed description and further info should be provided in the body (ie. `code-server: python2 add python-pip`). +``` +Request 1 ──▶ 202 (queued, position 1) +Request 2 ──▶ 202 (queued, position 2) + [worker runs command 1, then command 2] +``` -## Appendix +**202 Response:** -### File encoding +```json +{ + "status": "queued", + "command": "import", + "args": ["/downloads/album"], + "queue_size": 1 +} +``` -s6 init files must be encoded in plain `UTF-8`, and not `UTF-8 with BOM`. You can remove the BOM using your IDE if necessary. +## Lidarr Integration -### Inspecting mods +Use beets-httpshell as a Lidarr custom script to automatically import downloads. In Lidarr, go to **Settings → Connect → +** and add a **Custom Script** with the path to the script below. -To inspect the file contents of external Mods dive is a great CLI tool: +Create the script at a path accessible to Lidarr (e.g., `/config/scripts/beets-import.sh`): -[https://github.com/wagoodman/dive](https://github.com/wagoodman/dive) +```bash +#!/usr/bin/env bash -Basic usage: +if [ -z "$lidarr_artist_path" ]; then + echo "Error: lidarr_artist_path environment variable not set" + echo "Available environment variables:" + env | grep -i lidarr + exit 1 +fi -#### With Docker +curl -X POST --fail-with-body \ + -H "Content-Type: application/json" \ + -d "[\"$lidarr_artist_path\"]" \ + 'http://beets:5555/import?mode=queued' -```bash -docker run --rm -it \ - -v /var/run/docker.sock:/var/run/docker.sock \ - wagoodman/dive:latest +if [ $? -ne 0 ]; then + echo "Import request failed" + exit 1 +fi ``` -#### Without Docker +> **Note:** The script uses `?mode=queued` so Lidarr gets an immediate 202 response and doesn't block while beets processes the import. Adjust the hostname (`beets`) and port (`5555`) to match your setup. -```bash -dive +## Mod Structure + +```text +root/ +├── usr/local/bin/ +│ └── beets-httpshell.py # HTTP server script +└── etc/s6-overlay/s6-rc.d/ + ├── init-mod-beets-httpshell/ # oneshot init (startup banner, env validation) + ├── svc-mod-beets-httpshell/ # longrun service (HTTP server) + ├── init-mods-end/dependencies.d/ + │ └── init-mod-beets-httpshell + └── user/contents.d/ + ├── init-mod-beets-httpshell + └── svc-mod-beets-httpshell ``` diff --git a/blacklist.txt b/blacklist.txt deleted file mode 100644 index 9cce7e51a..000000000 --- a/blacklist.txt +++ /dev/null @@ -1,2 +0,0 @@ -evilbitcoinminer -rootkitinc diff --git a/mod-list.yml b/mod-list.yml deleted file mode 100644 index 0d68c534b..000000000 --- a/mod-list.yml +++ /dev/null @@ -1,220 +0,0 @@ -mods: - beets: - mod_count: 1 - container_mods: - - drop2beets: https://github.com/linuxserver/docker-mods/tree/beets-drop2beets - calibre-web: - mod_count: 3 - container_mods: - - calibre: https://github.com/linuxserver/docker-mods/tree/universal-calibre - - dtrpg-metadata: https://github.com/linuxserver/docker-mods/tree/calibre-web-dtrpg-metadata - - kcc: https://github.com/linuxserver/docker-mods/tree/calibre-web-kcc - code-server: - mod_count: 31 - container_mods: - - awscli: https://github.com/linuxserver/docker-mods/tree/code-server-awscli - - bat: https://github.com/linuxserver/docker-mods/tree/code-server-bat - - docker: https://github.com/linuxserver/docker-mods/tree/universal-docker - - docker-in-docker: https://github.com/linuxserver/docker-mods/tree/universal-docker-in-docker - - dotnet: https://github.com/linuxserver/docker-mods/tree/code-server-dotnet - - extension-arguments: https://github.com/linuxserver/docker-mods/tree/code-server-extension-arguments - - flutter: https://github.com/linuxserver/docker-mods/tree/code-server-flutter - - golang: https://github.com/linuxserver/docker-mods/tree/code-server-golang - - java11: https://github.com/linuxserver/docker-mods/tree/code-server-java11 - - julia: https://github.com/linuxserver/docker-mods/tree/code-server-julia - - nodejs: https://github.com/linuxserver/docker-mods/tree/code-server-nodejs - - npmglobal: https://github.com/linuxserver/docker-mods/tree/code-server-npmglobal - - nvm: https://github.com/linuxserver/docker-mods/tree/code-server-nvm - - php: https://github.com/linuxserver/docker-mods/tree/code-server-php - - php8: https://github.com/linuxserver/docker-mods/tree/code-server-php8 - - php-cli: https://github.com/linuxserver/docker-mods/tree/code-server-php-cli - - pnpm: https://github.com/linuxserver/docker-mods/tree/code-server-pnpm - - powershell: https://github.com/linuxserver/docker-mods/tree/code-server-powershell - - prolog: https://github.com/linuxserver/docker-mods/tree/code-server-prolog - - python3: https://github.com/linuxserver/docker-mods/tree/code-server-python3 - - python3-poetry: https://github.com/linuxserver/docker-mods/tree/code-server-python3-poetry - - r: https://github.com/linuxserver/docker-mods/tree/code-server-r - - rbenv: https://github.com/linuxserver/docker-mods/tree/code-server-rbenv - - ros2: https://github.com/linuxserver/docker-mods/tree/code-server-ros2 - - rust: https://github.com/linuxserver/docker-mods/tree/code-server-rust - - scikit-learn: https://github.com/linuxserver/docker-mods/tree/code-server-scikit-learn - - shellcheck: https://github.com/linuxserver/docker-mods/tree/code-server-shellcheck - - ssl: https://github.com/linuxserver/docker-mods/tree/code-server-ssl - - svn: https://github.com/linuxserver/docker-mods/tree/code-server-svn - - terraform: https://github.com/linuxserver/docker-mods/tree/code-server-terraform - - zsh: https://github.com/linuxserver/docker-mods/tree/code-server-zsh - emby: - mod_count: 1 - container_mods: - - mediainfo-plugin: https://github.com/linuxserver/docker-mods/tree/emby-mediainfo-plugin - firefox: - mod_count: 1 - container_mods: - - fonts: https://github.com/linuxserver/docker-mods/tree/firefox-fonts - homeassistant: - mod_count: 2 - container_mods: - - hacs: https://github.com/linuxserver/docker-mods/tree/homeassistant-hacs - - heyu: https://github.com/linuxserver/docker-mods/tree/homeassistant-heyu - jellyfin: - mod_count: 3 - container_mods: - - amd: https://github.com/linuxserver/docker-mods/tree/jellyfin-amd - - opencl-intel: https://github.com/linuxserver/docker-mods/tree/jellyfin-opencl-intel - - rffmpeg: https://github.com/linuxserver/docker-mods/tree/jellyfin-rffmpeg - lazylibrarian: - mod_count: 2 - container_mods: - - calibre: https://github.com/linuxserver/docker-mods/tree/universal-calibre - - ffmpeg: https://github.com/linuxserver/docker-mods/tree/lazylibrarian-ffmpeg - lidarr: - mod_count: 1 - container_mods: - - flac2mp3: https://github.com/linuxserver/docker-mods/tree/lidarr-flac2mp3 - netbox: - mod_count: 1 - container_mods: - - slurpit: https://github.com/linuxserver/docker-mods/tree/netbox-slurpit - nextcloud: - mod_count: 3 - container_mods: - - mediadc: https://github.com/linuxserver/docker-mods/tree/nextcloud-mediadc - - memories: https://github.com/linuxserver/docker-mods/tree/nextcloud-memories - - notify-push: https://github.com/linuxserver/docker-mods/tree/nextcloud-notify-push - nginx: - mod_count: 5 - container_mods: - - auto-reload: https://github.com/linuxserver/docker-mods/tree/swag-auto-reload - - crowdsec: https://github.com/linuxserver/docker-mods/tree/swag-crowdsec - - imagemagick: https://github.com/linuxserver/docker-mods/tree/swag-imagemagick - - ioncube: https://github.com/linuxserver/docker-mods/tree/swag-ioncube - - proxy-confs: https://github.com/linuxserver/docker-mods/tree/nginx-proxy-confs - openssh-server: - mod_count: 4 - container_mods: - - autossh: https://github.com/linuxserver/docker-mods/tree/openssh-server-autossh - - git: https://github.com/linuxserver/docker-mods/tree/openssh-server-git - - rsync: https://github.com/linuxserver/docker-mods/tree/openssh-server-rsync - - ssh-tunnel: https://github.com/linuxserver/docker-mods/tree/openssh-server-ssh-tunnel - openvscode-server: - mod_count: 28 - container_mods: - - awscli: https://github.com/linuxserver/docker-mods/tree/code-server-awscli - - docker: https://github.com/linuxserver/docker-mods/tree/universal-docker - - docker-in-docker: https://github.com/linuxserver/docker-mods/tree/universal-docker-in-docker - - dotnet: https://github.com/linuxserver/docker-mods/tree/code-server-dotnet - - extension-arguments: https://github.com/linuxserver/docker-mods/tree/code-server-extension-arguments - - flutter: https://github.com/linuxserver/docker-mods/tree/code-server-flutter - - golang: https://github.com/linuxserver/docker-mods/tree/code-server-golang - - java11: https://github.com/linuxserver/docker-mods/tree/code-server-java11 - - julia: https://github.com/linuxserver/docker-mods/tree/code-server-julia - - nodejs: https://github.com/linuxserver/docker-mods/tree/code-server-nodejs - - npmglobal: https://github.com/linuxserver/docker-mods/tree/code-server-npmglobal - - nvm: https://github.com/linuxserver/docker-mods/tree/code-server-nvm - - php: https://github.com/linuxserver/docker-mods/tree/code-server-php - - php8: https://github.com/linuxserver/docker-mods/tree/code-server-php8 - - php-cli: https://github.com/linuxserver/docker-mods/tree/code-server-php-cli - - pnpm: https://github.com/linuxserver/docker-mods/tree/code-server-pnpm - - powershell: https://github.com/linuxserver/docker-mods/tree/code-server-powershell - - prolog: https://github.com/linuxserver/docker-mods/tree/code-server-prolog - - python3: https://github.com/linuxserver/docker-mods/tree/code-server-python3 - - python3-poetry: https://github.com/linuxserver/docker-mods/tree/code-server-python3-poetry - - r: https://github.com/linuxserver/docker-mods/tree/code-server-r - - ros2: https://github.com/linuxserver/docker-mods/tree/code-server-ros2 - - rust: https://github.com/linuxserver/docker-mods/tree/code-server-rust - - scikit-learn: https://github.com/linuxserver/docker-mods/tree/code-server-scikit-learn - - shellcheck: https://github.com/linuxserver/docker-mods/tree/code-server-shellcheck - - svn: https://github.com/linuxserver/docker-mods/tree/code-server-svn - - terraform: https://github.com/linuxserver/docker-mods/tree/code-server-terraform - - zsh: https://github.com/linuxserver/docker-mods/tree/code-server-zsh - plex: - mod_count: 2 - container_mods: - - absolute-hama: https://github.com/linuxserver/docker-mods/tree/plex-absolute-hama - - audnexus: https://github.com/linuxserver/docker-mods/tree/plex-audnexus - projectsend: - mod_count: 1 - container_mods: - - translations: https://github.com/linuxserver/docker-mods/tree/projectsend-translations - radarr: - mod_count: 1 - container_mods: - - striptracks: https://github.com/linuxserver/docker-mods/tree/radarr-striptracks - sonarr: - mod_count: 1 - container_mods: - - striptracks: https://github.com/linuxserver/docker-mods/tree/radarr-striptracks - swag: - mod_count: 14 - container_mods: - - auto-proxy: https://github.com/linuxserver/docker-mods/tree/swag-auto-proxy - - auto-reload: https://github.com/linuxserver/docker-mods/tree/swag-auto-reload - - auto-uptime-kuma: https://github.com/linuxserver/docker-mods/tree/swag-auto-uptime-kuma - - cloudflare-real-ip: https://github.com/linuxserver/docker-mods/tree/swag-cloudflare-real-ip - - crowdsec: https://github.com/linuxserver/docker-mods/tree/swag-crowdsec - - dashboard: https://github.com/linuxserver/docker-mods/tree/swag-dashboard - - dbip: https://github.com/linuxserver/docker-mods/tree/swag-dbip - - ffmpeg: https://github.com/linuxserver/docker-mods/tree/swag-ffmpeg - - geoip2influx: https://github.com/linuxserver/docker-mods/tree/swag-geoip2influx - - imagemagick: https://github.com/linuxserver/docker-mods/tree/swag-imagemagick - - ioncube: https://github.com/linuxserver/docker-mods/tree/swag-ioncube - - ipinfo: https://github.com/linuxserver/docker-mods/tree/swag-ipinfo - - maxmind: https://github.com/linuxserver/docker-mods/tree/swag-maxmind - - ondemand: https://github.com/linuxserver/docker-mods/tree/swag-ondemand - transmission: - mod_count: 4 - container_mods: - - env-var-settings: https://github.com/linuxserver/docker-mods/tree/transmission-env-var-settings - - floodui: https://github.com/linuxserver/docker-mods/tree/transmission-floodui - - transmissionic: https://github.com/linuxserver/docker-mods/tree/transmission-transmissionic - - trguing: https://github.com/linuxserver/docker-mods/tree/transmission-trguing - universal: - mod_count: 12 - container_mods: - - apprise: https://github.com/linuxserver/docker-mods/tree/universal-apprise - - calibre: https://github.com/linuxserver/docker-mods/tree/universal-calibre - - cloudflared: https://github.com/linuxserver/docker-mods/tree/universal-cloudflared - - cron: https://github.com/linuxserver/docker-mods/tree/universal-cron - - docker: https://github.com/linuxserver/docker-mods/tree/universal-docker - - docker-in-docker: https://github.com/linuxserver/docker-mods/tree/universal-docker-in-docker - - git: https://github.com/linuxserver/docker-mods/tree/universal-git - - internationalization: https://github.com/linuxserver/docker-mods/tree/universal-internationalization - - package-install: https://github.com/linuxserver/docker-mods/tree/universal-package-install - - stdout-logs: https://github.com/linuxserver/docker-mods/tree/universal-stdout-logs - - tshoot: https://github.com/linuxserver/docker-mods/tree/universal-tshoot - - unrar6: https://github.com/linuxserver/docker-mods/tree/universal-unrar6 - vscodium-web: - mod_count: 28 - container_mods: - - awscli: https://github.com/linuxserver/docker-mods/tree/code-server-awscli - - docker: https://github.com/linuxserver/docker-mods/tree/universal-docker - - docker-in-docker: https://github.com/linuxserver/docker-mods/tree/universal-docker-in-docker - - dotnet: https://github.com/linuxserver/docker-mods/tree/code-server-dotnet - - extension-arguments: https://github.com/linuxserver/docker-mods/tree/code-server-extension-arguments - - flutter: https://github.com/linuxserver/docker-mods/tree/code-server-flutter - - golang: https://github.com/linuxserver/docker-mods/tree/code-server-golang - - java11: https://github.com/linuxserver/docker-mods/tree/code-server-java11 - - julia: https://github.com/linuxserver/docker-mods/tree/code-server-julia - - nodejs: https://github.com/linuxserver/docker-mods/tree/code-server-nodejs - - npmglobal: https://github.com/linuxserver/docker-mods/tree/code-server-npmglobal - - nvm: https://github.com/linuxserver/docker-mods/tree/code-server-nvm - - php: https://github.com/linuxserver/docker-mods/tree/code-server-php - - php8: https://github.com/linuxserver/docker-mods/tree/code-server-php8 - - php-cli: https://github.com/linuxserver/docker-mods/tree/code-server-php-cli - - pnpm: https://github.com/linuxserver/docker-mods/tree/code-server-pnpm - - powershell: https://github.com/linuxserver/docker-mods/tree/code-server-powershell - - prolog: https://github.com/linuxserver/docker-mods/tree/code-server-prolog - - python3: https://github.com/linuxserver/docker-mods/tree/code-server-python3 - - python3-poetry: https://github.com/linuxserver/docker-mods/tree/code-server-python3-poetry - - r: https://github.com/linuxserver/docker-mods/tree/code-server-r - - ros2: https://github.com/linuxserver/docker-mods/tree/code-server-ros2 - - rust: https://github.com/linuxserver/docker-mods/tree/code-server-rust - - scikit-learn: https://github.com/linuxserver/docker-mods/tree/code-server-scikit-learn - - shellcheck: https://github.com/linuxserver/docker-mods/tree/code-server-shellcheck - - svn: https://github.com/linuxserver/docker-mods/tree/code-server-svn - - terraform: https://github.com/linuxserver/docker-mods/tree/code-server-terraform - - zsh: https://github.com/linuxserver/docker-mods/tree/code-server-zsh - wireguard: - mod_count: 1 - container_mods: - - mullvad: https://github.com/linuxserver/docker-mods/tree/wireguard-mullvad diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/dependencies.d/init-mods b/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/dependencies.d/init-mods new file mode 100644 index 000000000..e69de29bb diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/run b/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/run new file mode 100755 index 000000000..df9749451 --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/run @@ -0,0 +1,5 @@ +#!/usr/bin/with-contenv bash + +echo "**** installing beets-httpshell mod ****" +echo "**** httpshell port: ${HTTPSHELL_PORT:-5555} ****" +echo "**** beets-httpshell mod installed ****" diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/type b/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/type new file mode 100644 index 000000000..bdd22a185 --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/type @@ -0,0 +1 @@ +oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/up b/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/up new file mode 100644 index 000000000..9873bf23d --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-mod-beets-httpshell/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mod-beets-httpshell b/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mod-beets-httpshell new file mode 100644 index 000000000..e69de29bb diff --git a/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/dependencies.d/init-services b/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/dependencies.d/init-services new file mode 100644 index 000000000..e69de29bb diff --git a/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/run b/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/run new file mode 100755 index 000000000..b921aec12 --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/run @@ -0,0 +1,3 @@ +#!/usr/bin/with-contenv bash + +exec s6-setuidgid abc python3 /usr/local/bin/beets-httpshell.py diff --git a/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/type b/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/type new file mode 100644 index 000000000..5883cff0c --- /dev/null +++ b/root/etc/s6-overlay/s6-rc.d/svc-mod-beets-httpshell/type @@ -0,0 +1 @@ +longrun diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-beets-httpshell b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-mod-beets-httpshell new file mode 100644 index 000000000..e69de29bb diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-beets-httpshell b/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-mod-beets-httpshell new file mode 100644 index 000000000..e69de29bb diff --git a/root/usr/local/bin/beets-httpshell.py b/root/usr/local/bin/beets-httpshell.py new file mode 100755 index 000000000..d7e1521da --- /dev/null +++ b/root/usr/local/bin/beets-httpshell.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +"""Lightweight HTTP server that proxies requests to beet CLI commands.""" + +import json +import os +import subprocess +import sys +import threading +import queue +from http.server import HTTPServer, BaseHTTPRequestHandler +from socketserver import ThreadingMixIn +from urllib.parse import urlparse, parse_qs + +BEET_CMD = os.environ.get("BEET_CMD", "/lsiopy/bin/beet") +BEET_CONFIG = os.environ.get("BEET_CONFIG", "/config/config.yaml") +PORT = int(os.environ.get("HTTPSHELL_PORT", "5555")) +BLOCKING_TIMEOUT = int(os.environ.get("HTTPSHELL_BLOCKING_TIMEOUT", "30")) +DEFAULT_MODE = "blocking" + +job_queue = queue.Queue() +blocking_lock = threading.Lock() + + +def run_beet(command, args): + """Execute a beet CLI command and return exit_code, stdout, stderr.""" + cmd = [BEET_CMD, "-c", BEET_CONFIG, command] + args + try: + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=3600 + ) + return result.returncode, result.stdout, result.stderr + except subprocess.TimeoutExpired: + return -1, "", "Command timed out after 3600 seconds" + except FileNotFoundError: + return -1, "", f"Command not found: {BEET_CMD}" + + +def queue_worker(): + """Background worker that processes queued jobs sequentially.""" + while True: + command, args = job_queue.get() + try: + run_beet(command, args) + except Exception as e: + print(f"[httpshell] queued job failed: {command} {args}: {e}", file=sys.stderr) + finally: + job_queue.task_done() + + +class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + parsed = urlparse(self.path) + if parsed.path.rstrip("/") in ("", "/health"): + self._send_json(200, { + "status": "ok", + "default_mode": DEFAULT_MODE, + "queue_size": job_queue.qsize(), + }) + else: + self._send_json(405, {"error": "Use POST to execute commands"}) + + def do_POST(self): + parsed = urlparse(self.path) + path = parsed.path.strip("/") + if not path: + self._send_json(400, {"error": "No command specified. Use POST /"}) + return + + # Parse mode from query parameter, default to blocking + params = parse_qs(parsed.query) + mode = params.get("mode", [DEFAULT_MODE])[0].lower() + if mode not in ("async", "queued", "blocking"): + self._send_json(400, {"error": f"Invalid mode '{mode}'. Use async, queued, or blocking"}) + return + + # First path segment is the beet subcommand + parts = path.split("/") + command = parts[0] + + # Parse JSON body for additional arguments + args = [] + content_length = int(self.headers.get("Content-Length", 0)) + if content_length > 0: + body = self.rfile.read(content_length) + try: + parsed_body = json.loads(body) + if isinstance(parsed_body, list): + args = [str(a) for a in parsed_body] + else: + self._send_json(400, {"error": "Request body must be a JSON array of arguments"}) + return + except json.JSONDecodeError as e: + self._send_json(400, {"error": f"Invalid JSON: {e}"}) + return + + # Add any extra path segments as arguments too + if len(parts) > 1: + args = list(parts[1:]) + args + + if mode == "queued": + self._handle_queued(command, args) + elif mode == "blocking": + self._handle_blocking(command, args) + else: + self._handle_async(command, args) + + def _handle_async(self, command, args): + """Run command immediately in the handler thread, return result.""" + exit_code, stdout, stderr = run_beet(command, args) + self._send_json(200, { + "command": command, + "args": args, + "exit_code": exit_code, + "stdout": stdout, + "stderr": stderr, + }) + + def _handle_queued(self, command, args): + """Queue command and return 202 immediately.""" + job_queue.put((command, args)) + self._send_json(202, { + "status": "queued", + "command": command, + "args": args, + "queue_size": job_queue.qsize(), + }) + + def _handle_blocking(self, command, args): + """Wait for lock; if acquired run command, otherwise queue it.""" + acquired = blocking_lock.acquire(timeout=BLOCKING_TIMEOUT) + if acquired: + try: + exit_code, stdout, stderr = run_beet(command, args) + self._send_json(200, { + "command": command, + "args": args, + "exit_code": exit_code, + "stdout": stdout, + "stderr": stderr, + }) + finally: + blocking_lock.release() + else: + job_queue.put((command, args)) + self._send_json(202, { + "status": "queued", + "message": f"Lock not acquired within {BLOCKING_TIMEOUT}s, job queued", + "command": command, + "args": args, + "queue_size": job_queue.qsize(), + }) + + def _send_json(self, status_code, data): + body = json.dumps(data).encode("utf-8") + self.send_response(status_code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, fmt, *args): + print(f"[httpshell] {self.address_string()} - {fmt % args}", file=sys.stderr) + + +class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): + daemon_threads = True + + +def main(): + # Always start queue worker (needed for queued and blocking-timeout fallback) + worker = threading.Thread(target=queue_worker, daemon=True) + worker.start() + + server = ThreadingHTTPServer(("0.0.0.0", PORT), RequestHandler) + print(f"[httpshell] Starting server on port {PORT} (default mode: {DEFAULT_MODE})", file=sys.stderr) + print(f"[httpshell] beet command: {BEET_CMD} -c {BEET_CONFIG}", file=sys.stderr) + + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + server.server_close() + print("[httpshell] Server stopped", file=sys.stderr) + + +if __name__ == "__main__": + main() From cd35c98a20a4ba1c3e99dbec4b1993abadc0c0b0 Mon Sep 17 00:00:00 2001 From: Volodymyr Dyptan Date: Sat, 7 Mar 2026 11:35:29 +0100 Subject: [PATCH 2/2] Test docker build --- .github/workflows/BuildImage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/BuildImage.yml b/.github/workflows/BuildImage.yml index 88789b857..04e366b10 100644 --- a/.github/workflows/BuildImage.yml +++ b/.github/workflows/BuildImage.yml @@ -34,7 +34,7 @@ jobs: MOD_VERSION: ${{ steps.outputs.outputs.MOD_VERSION }} build: - if: ${{ github.base_ref != 'master' }} + # if: ${{ github.base_ref != 'master' }} uses: linuxserver/github-workflows/.github/workflows/docker-mod-builder.yml@v1 needs: set-vars secrets: