nbdmux 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ name: ci
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ # Refuse to re-release a version PyPI already has. PyPI is immutable, but a
14
+ # retag-after-publish would rebuild the GitHub release assets from a newer
15
+ # commit and silently drift from the frozen wheel. (bty/withcache integrity
16
+ # guard, mirrored.)
17
+ check-not-published:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - name: Refuse if version is already on PyPI
22
+ if: startsWith(github.ref, 'refs/tags/v')
23
+ env:
24
+ TAG: ${{ github.ref_name }}
25
+ run: |
26
+ set -euo pipefail
27
+ version="${TAG#v}"
28
+ if curl -sfL "https://pypi.org/pypi/nbdmux/json" \
29
+ | python3 -c "import sys, json; sys.exit(0 if '${version}' in json.load(sys.stdin)['releases'] else 1)"; then
30
+ echo "::error::v${version} is already on PyPI; refusing to re-release."
31
+ exit 1
32
+ fi
33
+ echo "v${version} not on PyPI; proceeding."
34
+
35
+ lint:
36
+ needs: check-not-published
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ - uses: actions/setup-python@v5
41
+ with:
42
+ python-version: "3.12"
43
+ - run: pip install ruff
44
+ - run: make lint format-check
45
+
46
+ test:
47
+ needs: check-not-published
48
+ strategy:
49
+ matrix:
50
+ os: [ubuntu-latest]
51
+ python: ["3.10", "3.11", "3.12", "3.13"]
52
+ runs-on: ${{ matrix.os }}
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+ - uses: actions/setup-python@v5
56
+ with:
57
+ python-version: ${{ matrix.python }}
58
+ # Real nbd-server isn't spawned in unit tests (handlers use a fake)
59
+ # so we don't install it on the runner. The deploy/Containerfile is
60
+ # what ships it on the operator's host.
61
+ - run: make test
62
+
63
+ wheels:
64
+ needs: [lint, test]
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ - uses: actions/setup-python@v5
69
+ with:
70
+ python-version: "3.12"
71
+ - run: pip install build
72
+ - run: make wheel
73
+ - uses: actions/upload-artifact@v4
74
+ with:
75
+ name: dist
76
+ path: dist/
77
+
78
+ tag-release:
79
+ needs: [lint, test, wheels]
80
+ if: github.ref == 'refs/heads/main'
81
+ runs-on: ubuntu-latest
82
+ permissions:
83
+ contents: write
84
+ steps:
85
+ - uses: actions/checkout@v4
86
+ with:
87
+ fetch-depth: 0
88
+ token: ${{ secrets.RELEASE_PAT }}
89
+ - name: Tag the version if it is new
90
+ run: |
91
+ set -euo pipefail
92
+ version=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/nbdmux/__init__.py)
93
+ test -n "$version" || { echo "could not read __version__"; exit 1; }
94
+ tag="v${version}"
95
+ if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
96
+ echo "${tag} already exists; nothing to do."
97
+ exit 0
98
+ fi
99
+ git tag "${tag}"
100
+ git push origin "${tag}"
101
+ echo "Tagged ${tag}; ci-cd.yml will publish it."
102
+
103
+ publish-pypi:
104
+ needs: [lint, test, wheels]
105
+ if: startsWith(github.ref, 'refs/tags/v')
106
+ runs-on: ubuntu-latest
107
+ environment: pypi
108
+ permissions:
109
+ id-token: write
110
+ steps:
111
+ - uses: actions/checkout@v4
112
+ - uses: actions/setup-python@v5
113
+ with:
114
+ python-version: "3.12"
115
+ - run: pip install build
116
+ - run: make wheel
117
+ - uses: pypa/gh-action-pypi-publish@release/v1
118
+ with:
119
+ packages-dir: dist/
120
+
121
+ publish-image:
122
+ needs: [lint, test]
123
+ if: startsWith(github.ref, 'refs/tags/v')
124
+ runs-on: ubuntu-latest
125
+ permissions:
126
+ packages: write
127
+ contents: read
128
+ steps:
129
+ - uses: actions/checkout@v4
130
+ - uses: docker/setup-qemu-action@v3
131
+ - uses: docker/setup-buildx-action@v3
132
+ - uses: docker/login-action@v3
133
+ with:
134
+ registry: ghcr.io
135
+ username: ${{ github.actor }}
136
+ password: ${{ secrets.GITHUB_TOKEN }}
137
+ - name: Build and push multi-arch image
138
+ uses: docker/build-push-action@v6
139
+ with:
140
+ context: .
141
+ file: deploy/Containerfile
142
+ platforms: linux/amd64,linux/arm64
143
+ push: true
144
+ tags: |
145
+ ghcr.io/safl/nbdmux:latest
146
+ ghcr.io/safl/nbdmux:${{ github.ref_name }}
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ data/
9
+ images/
10
+ *.tmp
nbdmux-0.1.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Simon A. F. Lund
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
nbdmux-0.1.0/Makefile ADDED
@@ -0,0 +1,81 @@
1
+ # nbdmux -- common tasks (and the home of the CI logic: the GitHub workflows
2
+ # call these targets, so everything CI does is reproducible locally).
3
+ # Run `make` for the list. Override vars on the CLI, e.g.
4
+ # make serve PORT=4040 make bump VERSION=0.2.0
5
+ PYTHON ?= python3
6
+ RUFF ?= ruff
7
+ PRECOMMIT ?= pre-commit
8
+ PORT ?= 4040
9
+ NBD_PORT ?= 10809
10
+ # Containerized deploy: prefer podman, fall back to docker.
11
+ COMPOSE ?= $(shell command -v podman >/dev/null 2>&1 && echo podman || echo docker) compose
12
+ COMPOSE_FILE = deploy/compose.yml
13
+
14
+ # Single source of truth = src/nbdmux/__init__.py; pyproject derives it via Hatch.
15
+ SRC_VERSION = $(shell sed -n 's/^__version__ = "\(.*\)"/\1/p' src/nbdmux/__init__.py)
16
+
17
+ .DEFAULT_GOAL := help
18
+ .PHONY: help dev hooks-install hooks lint format format-check test \
19
+ wheel serve up down logs version bump check clean
20
+
21
+ help: ## Show this help
22
+ @grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) \
23
+ | awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'
24
+
25
+ # -- dev setup -------------------------------------------------------------
26
+ dev: ## Install dev tooling (ruff, build, pre-commit)
27
+ $(PYTHON) -m pip install --upgrade ruff build pre-commit
28
+
29
+ hooks-install: ## Install the git pre-commit hook
30
+ $(PRECOMMIT) install
31
+
32
+ hooks: ## Run all pre-commit hooks over the tree
33
+ $(PRECOMMIT) run --all-files
34
+
35
+ # -- lint / test (CI: lint job, test job) ----------------------------------
36
+ lint: ## Lint with ruff
37
+ $(RUFF) check .
38
+
39
+ format: ## Auto-format with ruff
40
+ $(RUFF) format .
41
+
42
+ format-check: ## Check formatting (no changes)
43
+ $(RUFF) format --check .
44
+
45
+ test: ## Run the test suite
46
+ $(PYTHON) -m unittest discover -s tests -v
47
+
48
+ # -- wheels / sdist --------------------------------------------------------
49
+ wheel: ## Build sdist + pure wheel
50
+ $(PYTHON) -m build
51
+
52
+ # -- run -------------------------------------------------------------------
53
+ serve: ## Run nbdmux locally (set NBDMUX_ADMIN_PASSWORD to gate the UI)
54
+ PYTHONPATH=src $(PYTHON) -m nbdmux.server --data-dir ./data --port $(PORT) --nbd-port $(NBD_PORT)
55
+
56
+ # -- deploy (containerized via compose) ------------------------------------
57
+ up: ## Bring up the containerized nbdmux
58
+ $(COMPOSE) -f $(COMPOSE_FILE) up -d --build
59
+ @echo "nbdmux up -> operator UI: http://localhost:$(PORT)/ NBD: tcp://localhost:$(NBD_PORT)"
60
+
61
+ down: ## Stop and remove the nbdmux container
62
+ $(COMPOSE) -f $(COMPOSE_FILE) down
63
+
64
+ logs: ## Follow the nbdmux logs
65
+ $(COMPOSE) -f $(COMPOSE_FILE) logs -f
66
+
67
+ # -- version (single source: src/nbdmux/__init__.py) -----------------------
68
+ version: ## Show the version
69
+ @echo "src/nbdmux/__init__.py: $(SRC_VERSION)"
70
+
71
+ bump: ## Bump the version (usage: make bump VERSION=0.2.0)
72
+ @test -n "$(VERSION)" || { echo "usage: make bump VERSION=X.Y.Z"; exit 2; }
73
+ sed -i 's/^__version__ = ".*"/__version__ = "$(VERSION)"/' src/nbdmux/__init__.py
74
+ @$(MAKE) --no-print-directory version
75
+
76
+ # -- aggregate / cleanup ---------------------------------------------------
77
+ check: lint format-check test ## Everything CI checks, locally
78
+
79
+ clean: ## Remove build/test artifacts
80
+ rm -rf dist build *.egg-info .pytest_cache
81
+ find . -type d -name __pycache__ -prune -exec rm -rf {} +
nbdmux-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: nbdmux
3
+ Version: 0.1.0
4
+ Summary: HTTP-controlled NBD-export multiplexer for a small lab (boot Linux images over the network with overlayfs+tmpfs writes)
5
+ Project-URL: Homepage, https://github.com/safl/nbdmux
6
+ Author-email: "Simon A. F. Lund" <safl@safl.dk>
7
+ License: BSD-3-Clause
8
+ License-File: LICENSE
9
+ Keywords: lab,nbd,netboot,overlayfs,ramboot
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: System :: Boot
16
+ Classifier: Topic :: System :: Filesystems
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+
20
+ # nbdmux
21
+
22
+ HTTP-controlled NBD-export multiplexer for a small lab. Register local
23
+ disk-image files as named NBD exports over an HTTP control plane; nbdmux
24
+ keeps an `nbd-server` subprocess alive that serves all registered
25
+ exports on a single TCP port. Targets `nbd-client` against that port
26
+ from an initramfs and boot the image with overlayfs over tmpfs for
27
+ writes (see [bty][bty]'s `ramboot` boot mode for the canonical consumer).
28
+
29
+ Designed as a peer to [withcache][withcache]: small lab, single sidecar
30
+ container, no third-party Python deps. Operationally:
31
+
32
+ ```
33
+ [ bty-web ] --HTTP--> [ nbdmux ] --supervises--> [ nbd-server ]
34
+ | |
35
+ | TCP 10809
36
+ | |
37
+ v v
38
+ SQLite state [ target's
39
+ (exports table) nbd-client ]
40
+ ```
41
+
42
+ ## Components
43
+
44
+ | Path | What it is |
45
+ |----------------------------|-------------------------------------------------------------------------|
46
+ | `src/nbdmux/server.py` | The daemon. HTTP control plane + nbd-server subprocess management + operator UI |
47
+ | `src/nbdmux/client.py` | Stdlib-only Python client library for other tools |
48
+ | `deploy/Containerfile` | Single-image deploy (Python + nbd-server) |
49
+ | `deploy/compose.yml` | Reference compose stack |
50
+
51
+ ## System dependency
52
+
53
+ nbdmux runs `nbd-server` (from the classical `nbd` project) as a
54
+ subprocess. Install at the OS level:
55
+
56
+ ```sh
57
+ # Debian / Ubuntu
58
+ sudo apt install nbd-server
59
+
60
+ # Fedora
61
+ sudo dnf install nbd
62
+ ```
63
+
64
+ The container deploy bundles it. Also make sure the `nbd` kernel
65
+ module + `nbd-client` are available on the consuming Linux box (the
66
+ target you're booting); they're in the same `nbd` package.
67
+
68
+ ## Install
69
+
70
+ ```sh
71
+ pipx install nbdmux # or: uv tool install nbdmux
72
+ ```
73
+
74
+ Run the daemon (development; the container deploy is the recommended
75
+ production path):
76
+
77
+ ```sh
78
+ nbdmux-server --data-dir ./data --port 4040 --nbd-port 10809
79
+ ```
80
+
81
+ Register an image:
82
+
83
+ ```sh
84
+ curl -X POST http://localhost:4040/exports \
85
+ -H 'Content-Type: application/json' \
86
+ -d '{"name": "debian-sysdev", "file": "/path/to/debian-sysdev.img", "readonly": true}'
87
+ ```
88
+
89
+ Then on a target Linux box:
90
+
91
+ ```sh
92
+ modprobe nbd
93
+ nbd-client <nbdmux-host> 10809 -name debian-sysdev /dev/nbd0
94
+ fdisk -l /dev/nbd0 # the .img's partition table
95
+ ```
96
+
97
+ ## HTTP control plane
98
+
99
+ | Method | Path | Body | Returns |
100
+ |--------|---------------------|-----------------------------------------------|----------------|
101
+ | GET | `/exports` | - | array of exports |
102
+ | POST | `/exports` | `{name, file, readonly?: bool}` | the new export |
103
+ | DELETE | `/exports/{name}` | - | 204 |
104
+ | GET | `/healthz` | - | `ok` |
105
+ | GET | `/` | - | operator dashboard |
106
+
107
+ ## Auth
108
+
109
+ Single-tenant, server-signed cookie -- same pattern as withcache. Set
110
+ `NBDMUX_ADMIN_PASSWORD` to gate the operator UI + the HTTP control
111
+ plane. Unset = open with a startup warning.
112
+
113
+ The NBD port itself is unauthenticated (nbd-server's classical model);
114
+ LAN-only assumption, firewall is the operator's responsibility.
115
+
116
+ ## License
117
+
118
+ BSD-3-Clause.
119
+
120
+ [bty]: https://github.com/safl/bty
121
+ [withcache]: https://github.com/safl/withcache
nbdmux-0.1.0/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # nbdmux
2
+
3
+ HTTP-controlled NBD-export multiplexer for a small lab. Register local
4
+ disk-image files as named NBD exports over an HTTP control plane; nbdmux
5
+ keeps an `nbd-server` subprocess alive that serves all registered
6
+ exports on a single TCP port. Targets `nbd-client` against that port
7
+ from an initramfs and boot the image with overlayfs over tmpfs for
8
+ writes (see [bty][bty]'s `ramboot` boot mode for the canonical consumer).
9
+
10
+ Designed as a peer to [withcache][withcache]: small lab, single sidecar
11
+ container, no third-party Python deps. Operationally:
12
+
13
+ ```
14
+ [ bty-web ] --HTTP--> [ nbdmux ] --supervises--> [ nbd-server ]
15
+ | |
16
+ | TCP 10809
17
+ | |
18
+ v v
19
+ SQLite state [ target's
20
+ (exports table) nbd-client ]
21
+ ```
22
+
23
+ ## Components
24
+
25
+ | Path | What it is |
26
+ |----------------------------|-------------------------------------------------------------------------|
27
+ | `src/nbdmux/server.py` | The daemon. HTTP control plane + nbd-server subprocess management + operator UI |
28
+ | `src/nbdmux/client.py` | Stdlib-only Python client library for other tools |
29
+ | `deploy/Containerfile` | Single-image deploy (Python + nbd-server) |
30
+ | `deploy/compose.yml` | Reference compose stack |
31
+
32
+ ## System dependency
33
+
34
+ nbdmux runs `nbd-server` (from the classical `nbd` project) as a
35
+ subprocess. Install at the OS level:
36
+
37
+ ```sh
38
+ # Debian / Ubuntu
39
+ sudo apt install nbd-server
40
+
41
+ # Fedora
42
+ sudo dnf install nbd
43
+ ```
44
+
45
+ The container deploy bundles it. Also make sure the `nbd` kernel
46
+ module + `nbd-client` are available on the consuming Linux box (the
47
+ target you're booting); they're in the same `nbd` package.
48
+
49
+ ## Install
50
+
51
+ ```sh
52
+ pipx install nbdmux # or: uv tool install nbdmux
53
+ ```
54
+
55
+ Run the daemon (development; the container deploy is the recommended
56
+ production path):
57
+
58
+ ```sh
59
+ nbdmux-server --data-dir ./data --port 4040 --nbd-port 10809
60
+ ```
61
+
62
+ Register an image:
63
+
64
+ ```sh
65
+ curl -X POST http://localhost:4040/exports \
66
+ -H 'Content-Type: application/json' \
67
+ -d '{"name": "debian-sysdev", "file": "/path/to/debian-sysdev.img", "readonly": true}'
68
+ ```
69
+
70
+ Then on a target Linux box:
71
+
72
+ ```sh
73
+ modprobe nbd
74
+ nbd-client <nbdmux-host> 10809 -name debian-sysdev /dev/nbd0
75
+ fdisk -l /dev/nbd0 # the .img's partition table
76
+ ```
77
+
78
+ ## HTTP control plane
79
+
80
+ | Method | Path | Body | Returns |
81
+ |--------|---------------------|-----------------------------------------------|----------------|
82
+ | GET | `/exports` | - | array of exports |
83
+ | POST | `/exports` | `{name, file, readonly?: bool}` | the new export |
84
+ | DELETE | `/exports/{name}` | - | 204 |
85
+ | GET | `/healthz` | - | `ok` |
86
+ | GET | `/` | - | operator dashboard |
87
+
88
+ ## Auth
89
+
90
+ Single-tenant, server-signed cookie -- same pattern as withcache. Set
91
+ `NBDMUX_ADMIN_PASSWORD` to gate the operator UI + the HTTP control
92
+ plane. Unset = open with a startup warning.
93
+
94
+ The NBD port itself is unauthenticated (nbd-server's classical model);
95
+ LAN-only assumption, firewall is the operator's responsibility.
96
+
97
+ ## License
98
+
99
+ BSD-3-Clause.
100
+
101
+ [bty]: https://github.com/safl/bty
102
+ [withcache]: https://github.com/safl/withcache
@@ -0,0 +1,43 @@
1
+ # nbdmux container -- bundles the daemon + nbd-server + minimal Python.
2
+ #
3
+ # Build: podman build -t ghcr.io/safl/nbdmux:dev -f deploy/Containerfile .
4
+ # Run: see deploy/compose.yml for the production shape.
5
+ #
6
+ # Two ports:
7
+ # 4040 HTTP control plane + operator UI
8
+ # 10809 NBD (nbd-server)
9
+
10
+ FROM debian:trixie-slim
11
+
12
+ RUN apt-get update \
13
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
14
+ nbd-server \
15
+ python3 \
16
+ python3-pip \
17
+ ca-certificates \
18
+ && rm -rf /var/lib/apt/lists/*
19
+
20
+ WORKDIR /app
21
+ COPY pyproject.toml README.md LICENSE /app/
22
+ COPY src/ /app/src/
23
+ RUN pip install --break-system-packages --no-cache-dir .
24
+
25
+ # Persistent state lives under /data. The compose stack binds this to a
26
+ # named volume so a container rebuild keeps registered exports.
27
+ ENV NBDMUX_DATA_DIR=/data
28
+ VOLUME ["/data"]
29
+
30
+ # Image files we serve as NBD exports live under /images (a read-only
31
+ # bind from the host that holds the actual .img bytes). This is just
32
+ # the convention; the daemon will serve any absolute path the operator
33
+ # registers.
34
+ VOLUME ["/images"]
35
+
36
+ EXPOSE 4040 10809
37
+
38
+ HEALTHCHECK --interval=15s --timeout=3s --start-period=5s \
39
+ CMD python3 -c "import urllib.request, sys; \
40
+ sys.exit(0 if urllib.request.urlopen('http://localhost:4040/healthz', timeout=2).status == 200 else 1)"
41
+
42
+ ENTRYPOINT ["nbdmux-server"]
43
+ CMD ["--data-dir", "/data", "--port", "4040", "--nbd-port", "10809"]
@@ -0,0 +1,27 @@
1
+ # Reference compose stack -- run as a sidecar (e.g. next to bty + withcache).
2
+ #
3
+ # Usage:
4
+ # cp envvars.example envvars && "${EDITOR:-vi}" envvars # set NBDMUX_ADMIN_PASSWORD
5
+ # podman compose --env-file envvars up -d
6
+ # # http://<host>:4040/ (operator UI)
7
+ # # tcp://<host>:10809 (NBD)
8
+
9
+ services:
10
+ nbdmux:
11
+ image: ghcr.io/safl/nbdmux:latest
12
+ restart: unless-stopped
13
+ environment:
14
+ NBDMUX_ADMIN_PASSWORD: "${NBDMUX_ADMIN_PASSWORD:-}"
15
+ ports:
16
+ - "${HOST_HTTP_PORT:-4040}:4040"
17
+ - "${HOST_NBD_PORT:-10809}:10809"
18
+ volumes:
19
+ # Persistent: state.db + nbd-server.conf survive restarts here.
20
+ - nbdmux-data:/data
21
+ # Read-only bind of the host directory holding the .img files
22
+ # nbdmux will serve as NBD exports. Operator places files here
23
+ # (or, in the bty integration, bty-web decompresses into here).
24
+ - "${IMAGES_HOST_DIR:-./images}:/images:ro"
25
+
26
+ volumes:
27
+ nbdmux-data:
@@ -0,0 +1,16 @@
1
+ # Copy to ``envvars`` and edit. The compose stack reads this via --env-file.
2
+
3
+ # Gate the operator UI + control-plane writes. Empty = open (logs a
4
+ # startup warning; only safe on a LAN-internal nbdmux).
5
+ NBDMUX_ADMIN_PASSWORD=
6
+
7
+ # Optional: host ports to publish. Default: same as the in-container
8
+ # defaults (4040 HTTP, 10809 NBD).
9
+ #HOST_HTTP_PORT=4040
10
+ #HOST_NBD_PORT=10809
11
+
12
+ # Where the .img files live on the host. Bound read-only into the
13
+ # container at /images. nbdmux serves whatever absolute path the
14
+ # operator registers via POST /exports; using a single host directory
15
+ # for the .img files just keeps the bind-mount simple.
16
+ #IMAGES_HOST_DIR=./images
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "nbdmux"
7
+ dynamic = ["version"] # single source of truth: src/nbdmux/__init__.py:__version__
8
+ description = "HTTP-controlled NBD-export multiplexer for a small lab (boot Linux images over the network with overlayfs+tmpfs writes)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "BSD-3-Clause" }
12
+ authors = [{ name = "Simon A. F. Lund", email = "safl@safl.dk" }]
13
+ keywords = ["nbd", "netboot", "ramboot", "overlayfs", "lab"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: System Administrators",
18
+ "License :: OSI Approved :: BSD License",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: System :: Boot",
21
+ "Topic :: System :: Filesystems",
22
+ ]
23
+ dependencies = [] # stdlib only, by design (delegates to the system's nbd-server)
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/safl/nbdmux"
27
+
28
+ [project.scripts]
29
+ nbdmux-server = "nbdmux.server:main"
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/nbdmux"]
33
+
34
+ [tool.hatch.version]
35
+ path = "src/nbdmux/__init__.py"
36
+
37
+ [tool.ruff]
38
+ line-length = 100
39
+ target-version = "py310"
40
+
41
+ [tool.ruff.lint]
42
+ select = ["E", "F", "W", "I", "UP", "B"]
43
+
44
+ [tool.ruff.lint.per-file-ignores]
45
+ "tests/*" = ["E402"]
@@ -0,0 +1,23 @@
1
+ """nbdmux -- HTTP-controlled NBD-export multiplexer.
2
+
3
+ Two surfaces:
4
+
5
+ - ``nbdmux-server`` (``nbdmux.server:main``) -- the daemon. Manages an
6
+ ``nbd-server`` subprocess that exposes registered local files as
7
+ named NBD exports on a TCP port (default 10809). Operator dashboard
8
+ + HTTP control API on a separate port (default 4040).
9
+ - ``nbdmux.client`` -- a tiny stdlib-only library for other tools
10
+ (e.g. bty) to register / list / unregister exports without
11
+ reimplementing the HTTP API.
12
+
13
+ Designed for the same niche as ``withcache``: a small lab, a single
14
+ sidecar container, no third-party Python deps. The system-level
15
+ dependency is ``nbd-server`` (Debian / Ubuntu: ``apt install
16
+ nbd-server``; Fedora: ``dnf install nbd``).
17
+ """
18
+
19
+ from .client import add_export, list_exports, remove_export
20
+
21
+ __version__ = "0.1.0"
22
+
23
+ __all__ = ["__version__", "add_export", "list_exports", "remove_export"]