Skip to content

Commit e0aa7d4

Browse files
authored
Merge pull request #1134 from dyptan-io/beets-httpshell
beets: add simple http server to execute beets commands remotelly
2 parents 6360fcf + efde645 commit e0aa7d4

File tree

27 files changed

+442
-110
lines changed

27 files changed

+442
-110
lines changed

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
.github
44
.gitattributes
55
READMETEMPLATE.md
6-
README.md
6+
README.md

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
*.pdf diff=astextplain
1515
*.PDF diff=astextplain
1616
*.rtf diff=astextplain
17-
*.RTF diff=astextplain
17+
*.RTF diff=astextplain

.github/workflows/BuildImage.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ on:
1212
env:
1313
GITHUB_REPO: "linuxserver/docker-mods" #don't modify
1414
ENDPOINT: "linuxserver/mods" #don't modify
15-
BASEIMAGE: "replace_baseimage" #replace
16-
MODNAME: "replace_modname" #replace
15+
BASEIMAGE: "beets" #replace
16+
MODNAME: "httpshell" #replace
1717
MOD_VERSION: ${{ inputs.mod_version }} #don't modify
18-
MULTI_ARCH: "true" #set to false if not needed
18+
MULTI_ARCH: "false" #set to false if not needed
1919

2020
jobs:
2121
set-vars:
@@ -61,4 +61,4 @@ jobs:
6161
MODNAME: ${{ needs.set-vars.outputs.MODNAME }}
6262
MULTI_ARCH: ${{ needs.set-vars.outputs.MULTI_ARCH }}
6363
MOD_VERSION: ${{ needs.set-vars.outputs.MOD_VERSION }}
64-
MOD_VERSION_OVERRIDE: ${{ needs.set-vars.outputs.MOD_VERSION_OVERRIDE }}
64+
MOD_VERSION_OVERRIDE: ${{ needs.set-vars.outputs.MOD_VERSION_OVERRIDE }}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ on:
77
- '**/check'
88
jobs:
99
permission_check:
10-
uses: linuxserver/github-workflows/.github/workflows/init-svc-executable-permissions.yml@v1
10+
uses: linuxserver/github-workflows/.github/workflows/init-svc-executable-permissions.yml@v1

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ $RECYCLE.BIN/
4040
.AppleDesktop
4141
Network Trash Folder
4242
Temporary Items
43-
.apdisk
43+
.apdisk

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
FROM scratch
44

5-
LABEL maintainer="username"
5+
LABEL maintainer="dyptan-io"
66

77
# copy local files
8-
COPY root/ /
8+
COPY root/ /

Dockerfile.complex

Lines changed: 0 additions & 33 deletions
This file was deleted.

README.md

Lines changed: 211 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,219 @@
1-
# Rsync - Docker mod for openssh-server
1+
# beets-httpshell
22

3-
This mod adds rsync to openssh-server, to be installed/updated during container start.
3+
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.
44

5-
In openssh-server docker arguments, set an environment variable `DOCKER_MODS=linuxserver/mods:openssh-server-rsync`
5+
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.
66

7-
If adding multiple mods, enter them in an array separated by `|`, such as `DOCKER_MODS=linuxserver/mods:openssh-server-rsync|linuxserver/mods:openssh-server-mod2`
7+
> **⚠️ 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.
88
9-
# Mod creation instructions
9+
## Installation
1010

11-
* Fork the repo, create a new branch based on the branch `template`.
12-
* Edit the `Dockerfile` for the mod. `Dockerfile.complex` is only an example and included for reference; it should be deleted when done.
13-
* Inspect the `root` folder contents. Edit, add and remove as necessary.
14-
* 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.
15-
* Edit this readme with pertinent info, delete these instructions.
16-
* Finally edit the `.github/workflows/BuildImage.yml`. Customize the vars for `BASEIMAGE` and `MODNAME`. Set the versioning logic and `MULTI_ARCH` if needed.
17-
* Ask the team to create a new branch named `<baseimagename>-<modname>`. Baseimage should be the name of the image the mod will be applied to. The new branch will be based on the `template` branch.
18-
* Submit PR against the branch created by the team.
11+
1. Configure your selected Docker container with the port, volume, and environment settings from the *original container documentation* here **[linuxserver/beets](https://hub.docker.com/r/linuxserver/beets "Beets Docker container")**
12+
2. Add the **DOCKER_MODS** environment variable to your `compose.yml` file or `docker run` command, as follows:
13+
- `DOCKER_MODS=linuxserver/mods:beets-httpshell`
14+
3. Map the HTTP API port so it is accessible from outside the container. The default port is `5555` (configurable via `HTTPSHELL_PORT`). Add `5555:5555` to your port mappings:
1915

16+
<details>
17+
<summary>Example Docker Compose YAML Configuration</summary>
2018

21-
## Tips and tricks
19+
```yaml
20+
---
21+
services:
22+
beets:
23+
image: lscr.io/linuxserver/beets:latest
24+
container_name: beets
25+
environment:
26+
- PUID=1000
27+
- PGID=1000
28+
- TZ=Europe/London
29+
- DOCKER_MODS=linuxserver/mods:beets-httpshell
30+
- HTTPSHELL_PORT=5555
31+
volumes:
32+
- /path/to/config:/config
33+
- /path/to/music:/music
34+
- /path/to/downloads:/downloads
35+
ports:
36+
- 8337:8337
37+
- 5555:5555
38+
restart: unless-stopped
39+
```
40+
</details>
2241
23-
* Some images have helpers built in, these images are currently:
24-
* [Openvscode-server](https://github.com/linuxserver/docker-openvscode-server/pull/10/files)
25-
* [Code-server](https://github.com/linuxserver/docker-code-server/pull/95)
42+
<details>
43+
<summary>Example Docker Run Command</summary>
44+
45+
```bash
46+
docker run -d \
47+
--name=beets \
48+
-e PUID=1000 \
49+
-e PGID=1000 \
50+
-e TZ=Europe/London \
51+
-e DOCKER_MODS=linuxserver/mods:beets-httpshell \
52+
-e HTTPSHELL_PORT=5555 \
53+
-p 8337:8337 \
54+
-p 5555:5555 \
55+
-v /path/to/config:/config \
56+
-v /path/to/music:/music \
57+
-v /path/to/downloads:/downloads \
58+
--restart unless-stopped \
59+
lscr.io/linuxserver/beets:latest
60+
```
61+
62+
</details>
63+
64+
4. Start the container.
65+
66+
### Environment Variables
67+
68+
| Variable | Default | Description |
69+
|---|---|---|
70+
| `BEET_CMD` | `/lsiopy/bin/beet` | Path to the `beet` binary |
71+
| `BEET_CONFIG` | `/config/config.yaml` | Path to the beets config file |
72+
| `HTTPSHELL_PORT` | `5555` | Port the HTTP server listens on |
73+
| `HTTPSHELL_BLOCKING_TIMEOUT` | `30` | Seconds to wait for the lock in `block` mode before the job is queued |
74+
75+
## API Usage
76+
77+
### Execute a command
78+
79+
```
80+
POST /<command>
81+
Content-Type: application/json
82+
83+
["arg1", "arg2", ...]
84+
```
85+
86+
The URL path is the beet subcommand. The optional `?mode=` query parameter controls execution mode (`parallel`, `queue`, or `block` — defaults to `parallel`). The JSON body is an array of string arguments. An empty body or `[]` means no arguments.
87+
88+
**Response** (200 OK):
89+
90+
```json
91+
{
92+
"command": "stats",
93+
"args": [],
94+
"exit_code": 0,
95+
"stdout": "Tracks: 1234\nTotal time: 3.2 days\n...",
96+
"stderr": ""
97+
}
98+
```
99+
100+
### Examples
101+
102+
```bash
103+
# Get library stats (default parallel mode)
104+
curl -X POST http://localhost:5555/stats
105+
106+
# List all tracks by an artist
107+
curl -X POST http://localhost:5555/list \
108+
-H "Content-Type: application/json" \
109+
-d '["artist:Radiohead"]'
110+
111+
# Import music in parallel (returns result when done, runs in parallel with other requests)
112+
curl -X POST http://localhost:5555/import \
113+
-H "Content-Type: application/json" \
114+
-d '["--quiet", "--incremental", "/downloads/music"]'
115+
116+
# Queue an import (returns 202 immediately, runs in background)
117+
curl -X POST 'http://localhost:5555/import?mode=queue' \
118+
-H "Content-Type: application/json" \
119+
-d '["--quiet", "/downloads/music"]'
120+
121+
# Update the library
122+
curl -X POST http://localhost:5555/update
123+
124+
# Get beets configuration
125+
curl -X POST http://localhost:5555/config
126+
127+
# Remove tracks matching a query (force, delete files)
128+
curl -X POST http://localhost:5555/remove \
129+
-H "Content-Type: application/json" \
130+
-d '["artist:test", "-d", "-f"]'
131+
132+
# Move items to a new directory
133+
curl -X POST http://localhost:5555/move \
134+
-H "Content-Type: application/json" \
135+
-d '["artist:Radiohead", "-d", "/music/favorites"]'
136+
```
137+
138+
## Execution Modes
139+
140+
The execution mode is controlled per-request via the `?mode=` query parameter. If omitted, defaults to `parallel`.
141+
142+
### `parallel` (default)
143+
144+
Each request runs its command immediately in its own thread. Multiple commands execute in parallel. The response is returned when the command finishes.
145+
146+
```
147+
Request 1 ──▶ [runs command] ──▶ 200 response
148+
Request 2 ──▶ [runs command] ──▶ 200 response (runs in parallel)
149+
```
150+
151+
### `block`
152+
153+
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.
154+
155+
```
156+
Request 1 ──▶ [acquires lock, runs command] ──▶ 200 response
157+
Request 2 ──▶ [waits for lock... acquired] ──▶ 200 response
158+
Request 3 ──▶ [waits for lock... timeout] ──▶ 202 (queued)
159+
```
160+
161+
### `queue`
162+
163+
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`).
164+
165+
```
166+
Request 1 ──▶ 202 (queued, position 1)
167+
Request 2 ──▶ 202 (queued, position 2)
168+
[worker runs command 1, then command 2]
169+
```
170+
171+
**202 Response:**
172+
173+
```json
174+
{
175+
"status": "queued",
176+
"command": "import",
177+
"args": ["/downloads/album"],
178+
"queue_size": 1
179+
}
180+
```
181+
182+
## Lidarr Integration Example
183+
184+
Use remote beets HTTP server in Lidarr's external content management script to automatically import downloads. In Lidarr, go to **Settings → Media Management → Importing → +** and add a **Import Script Path** with the path to the script below.
185+
186+
Create the script at a path accessible to Lidarr (e.g., `/config/scripts/beets-import.sh`):
187+
188+
```bash
189+
#!/usr/bin/env bash
190+
191+
if [ -z "$lidarr_sourcepath" ]; then
192+
echo "Error: lidarr_sourcepath environment variable not set"
193+
exit 1
194+
fi
195+
196+
curl -X POST --fail-with-body \
197+
-H "Content-Type: application/json" \
198+
-d "[\"-q\",\"$lidarr_sourcepath\"]" \
199+
'http://beets:5555/import?mode=block'
200+
201+
if [ $? -ne 0 ]; then
202+
echo "Import request failed"
203+
exit 1
204+
fi
205+
```
206+
207+
> **Note:** The script uses `?mode=block` so Lidarr waits for the import to complete before proceeding. Without it, the default `parallel` mode would also work but allows concurrent imports and import changes may not be detected by Lidarr sync. Adjust the hostname (`beets`) and port (`5555`) to match your setup.
208+
209+
## Mod Structure
210+
211+
```text
212+
root/
213+
├── usr/local/bin/
214+
│ └── beets-httpshell.py # HTTP server script
215+
└── etc/s6-overlay/s6-rc.d/
216+
├── svc-mod-beets-httpshell/ # longrun service (HTTP server)
217+
└── user/contents.d/
218+
└── svc-mod-beets-httpshell
219+
```

root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/run

Lines changed: 0 additions & 30 deletions
This file was deleted.

root/etc/s6-overlay/s6-rc.d/init-mod-imagename-modname-add-package/type

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)