zboxapi 0.0.7__tar.gz → 0.1.1__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.
zboxapi-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: zboxapi
3
+ Version: 0.1.1
4
+ Summary: zPodFactory zBox API: DNS and VLAN management for the zbox VM
5
+ Author: Kelby Valenti, Timo Sugliani
6
+ Author-email: Kelby Valenti <kelby.valenti@gmail.com>, Timo Sugliani <timo.sugliani@gmail.com>
7
+ License-Expression: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.14
10
+ Classifier: Framework :: FastAPI
11
+ Classifier: Operating System :: POSIX :: Linux
12
+ Requires-Dist: fastapi>=0.118
13
+ Requires-Dist: pydantic>=2.12
14
+ Requires-Dist: uvicorn>=0.37
15
+ Requires-Python: >=3.14
16
+ Project-URL: Homepage, https://github.com/zPodFactory/zboxapi
17
+ Project-URL: Repository, https://github.com/zPodFactory/zboxapi
18
+ Project-URL: Changelog, https://github.com/zPodFactory/zboxapi/blob/main/CHANGELOG.md
19
+ Project-URL: Documentation, https://github.com/zPodFactory/zboxapi#readme
20
+ Project-URL: Issues, https://github.com/zPodFactory/zboxapi/issues
21
+ Description-Content-Type: text/markdown
22
+
23
+ # zBoxApi
24
+
25
+ zPodFactory zBox Api
26
+
27
+ ## Features
28
+
29
+ - **DNS Management**: Manage DNS records in `/etc/hosts` with automatic dnsmasq integration
30
+ - **VLAN Management**: Manage VLAN interfaces with automatic network configuration
31
+
32
+ ## Installation
33
+
34
+ Complete the following steps to set up zBox Api:
35
+
36
+ 1. Install uv
37
+
38
+ ```bash
39
+ curl -LsSf https://astral.sh/uv/install.sh | sh
40
+
41
+ # Reload your profile so ~/.local/bin is on the PATH
42
+ source ~/.zshrc
43
+ ```
44
+
45
+ 1. Install zBoxApi:
46
+
47
+ ```bash
48
+ uv tool install zboxapi
49
+ ```
50
+
51
+ zBoxApi requires **Python 3.14**. Debian ships an older interpreter, so install with
52
+ [uv](https://docs.astral.sh/uv/), which downloads a managed Python 3.14 on its own. `pipx
53
+ install zboxapi` only works where a 3.14 interpreter is already on the PATH.
54
+
55
+ 1. Set up and start zboxapi.service
56
+
57
+ ```bash
58
+ cp zboxapi.service /etc/systemd/system
59
+ systemctl daemon-reload
60
+ systemctl enable zboxapi.service
61
+ systemctl start zboxapi.service
62
+ ```
63
+
64
+ **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
65
+
66
+ ## Configuration
67
+
68
+ ### VLAN Management
69
+
70
+ For VLAN management functionality, create a configuration file at `/etc/zboxapi.conf`:
71
+
72
+ ```ini
73
+ [DEFAULT]
74
+ # Base interface name for VLAN management
75
+ interface = eth1
76
+
77
+ # MTU setting for VLAN interfaces
78
+ mtu = 1700
79
+
80
+ # System default VLANs that cannot be modified (comma-separated)
81
+ system_vlans_default = 10,20,30
82
+
83
+ # System zPod VLANs that cannot be modified (comma-separated)
84
+ system_vlans_zpod = 64,128,192
85
+ ```
86
+
87
+ See [DOC_VLAN.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_VLAN.md) for detailed documentation on VLAN management features.
88
+
89
+ ## API Usage
90
+
91
+ ### Authentication
92
+
93
+ All API endpoints require authentication using the `access_token` header. The API key is the zPod password which is also the root password of the zbox VM. The password is automatically retrieved from VMware tools:
94
+
95
+ ```bash
96
+ curl -H "access_token: your_zpod_password" http://127.0.0.1:8000/dns
97
+ ```
98
+
99
+ **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
100
+
101
+ ### DNS Management
102
+
103
+ Manage DNS records in `/etc/hosts`:
104
+
105
+ ```bash
106
+ # Add DNS record
107
+ curl -X POST "http://127.0.0.1:8000/dns" \
108
+ -H "access_token: your_zpod_password" \
109
+ -H "Content-Type: application/json" \
110
+ -d '{"ip": "192.168.1.100", "hostname": "example.com"}'
111
+
112
+ # List all DNS records
113
+ curl -X GET "http://127.0.0.1:8000/dns" \
114
+ -H "access_token: your_zpod_password"
115
+ ```
116
+
117
+ For complete DNS management documentation, see [DOC_DNS.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_DNS.md).
118
+
119
+ ### VLAN Management
120
+
121
+ Manage VLAN interfaces:
122
+
123
+ ```bash
124
+ # Create VLAN interface
125
+ curl -X POST "http://127.0.0.1:8000/vlan" \
126
+ -H "access_token: your_zpod_password" \
127
+ -H "Content-Type: application/json" \
128
+ -d '{"vlan": 2000, "gateway": "192.168.42.129/25"}'
129
+
130
+ # List all VLAN interfaces
131
+ curl -X GET "http://127.0.0.1:8000/vlan" \
132
+ -H "access_token: your_zpod_password"
133
+ ```
134
+
135
+ For complete VLAN management documentation, see [DOC_VLAN.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_VLAN.md).
136
+
137
+
138
+ ## Development
139
+
140
+ The project is managed with [uv](https://docs.astral.sh/uv/). Clone the repository, then:
141
+
142
+ ```bash
143
+ uv sync # create .venv with the project and dev dependencies
144
+ uv run pytest # run the unit tests (no root, /etc or network access needed)
145
+ uv run pytest --cov # same, with a coverage report
146
+ uv run ruff check src tests && uv run ruff format --check src tests
147
+ ```
148
+
149
+ A `justfile` wraps the same commands (`just test`, `just lint`, `just format`).
150
+
151
+ ### Releasing
152
+
153
+ Every change gets a line under `[Unreleased]` in [CHANGELOG.md](https://github.com/zPodFactory/zboxapi/blob/main/CHANGELOG.md). A release is
154
+ one command:
155
+
156
+ ```bash
157
+ python3 tools/release.py 0.2.0 --push # or: just release 0.2.0
158
+ ```
159
+
160
+ It turns `[Unreleased]` into a dated `[0.2.0]` section, bumps `pyproject.toml` and `uv.lock`,
161
+ runs the tests, commits, tags `v0.2.0` and pushes. The tag then runs
162
+ `.github/workflows/release.yml`, which publishes the changelog section as the GitHub release
163
+ note, builds the package with `uv build`, publishes it to PyPI with `uv publish` and attaches
164
+ the wheel and sdist to the release. See [tools/README.md](https://github.com/zPodFactory/zboxapi/blob/main/tools/README.md) for the details,
165
+ including the one-time PyPI setup (an API token secret or trusted publishing).
166
+
167
+ The test suite exercises every endpoint through FastAPI's `TestClient`. The hosts file,
168
+ `/etc/zboxapi.conf`, `/etc/network/interfaces.d/` and the `vmtoolsd` password lookup are
169
+ redirected to temporary locations, and the `ip`, `ifup`, `ifdown` and `pkill` commands are
170
+ replaced by an in-memory fake, so the tests can run on any machine.
171
+
172
+ ## Documentation
173
+
174
+ - [DOC_DNS.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_DNS.md) - Complete guide to DNS management features
175
+ - [DOC_VLAN.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_VLAN.md) - Complete guide to VLAN management features
@@ -0,0 +1,153 @@
1
+ # zBoxApi
2
+
3
+ zPodFactory zBox Api
4
+
5
+ ## Features
6
+
7
+ - **DNS Management**: Manage DNS records in `/etc/hosts` with automatic dnsmasq integration
8
+ - **VLAN Management**: Manage VLAN interfaces with automatic network configuration
9
+
10
+ ## Installation
11
+
12
+ Complete the following steps to set up zBox Api:
13
+
14
+ 1. Install uv
15
+
16
+ ```bash
17
+ curl -LsSf https://astral.sh/uv/install.sh | sh
18
+
19
+ # Reload your profile so ~/.local/bin is on the PATH
20
+ source ~/.zshrc
21
+ ```
22
+
23
+ 1. Install zBoxApi:
24
+
25
+ ```bash
26
+ uv tool install zboxapi
27
+ ```
28
+
29
+ zBoxApi requires **Python 3.14**. Debian ships an older interpreter, so install with
30
+ [uv](https://docs.astral.sh/uv/), which downloads a managed Python 3.14 on its own. `pipx
31
+ install zboxapi` only works where a 3.14 interpreter is already on the PATH.
32
+
33
+ 1. Set up and start zboxapi.service
34
+
35
+ ```bash
36
+ cp zboxapi.service /etc/systemd/system
37
+ systemctl daemon-reload
38
+ systemctl enable zboxapi.service
39
+ systemctl start zboxapi.service
40
+ ```
41
+
42
+ **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
43
+
44
+ ## Configuration
45
+
46
+ ### VLAN Management
47
+
48
+ For VLAN management functionality, create a configuration file at `/etc/zboxapi.conf`:
49
+
50
+ ```ini
51
+ [DEFAULT]
52
+ # Base interface name for VLAN management
53
+ interface = eth1
54
+
55
+ # MTU setting for VLAN interfaces
56
+ mtu = 1700
57
+
58
+ # System default VLANs that cannot be modified (comma-separated)
59
+ system_vlans_default = 10,20,30
60
+
61
+ # System zPod VLANs that cannot be modified (comma-separated)
62
+ system_vlans_zpod = 64,128,192
63
+ ```
64
+
65
+ See [DOC_VLAN.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_VLAN.md) for detailed documentation on VLAN management features.
66
+
67
+ ## API Usage
68
+
69
+ ### Authentication
70
+
71
+ All API endpoints require authentication using the `access_token` header. The API key is the zPod password which is also the root password of the zbox VM. The password is automatically retrieved from VMware tools:
72
+
73
+ ```bash
74
+ curl -H "access_token: your_zpod_password" http://127.0.0.1:8000/dns
75
+ ```
76
+
77
+ **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
78
+
79
+ ### DNS Management
80
+
81
+ Manage DNS records in `/etc/hosts`:
82
+
83
+ ```bash
84
+ # Add DNS record
85
+ curl -X POST "http://127.0.0.1:8000/dns" \
86
+ -H "access_token: your_zpod_password" \
87
+ -H "Content-Type: application/json" \
88
+ -d '{"ip": "192.168.1.100", "hostname": "example.com"}'
89
+
90
+ # List all DNS records
91
+ curl -X GET "http://127.0.0.1:8000/dns" \
92
+ -H "access_token: your_zpod_password"
93
+ ```
94
+
95
+ For complete DNS management documentation, see [DOC_DNS.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_DNS.md).
96
+
97
+ ### VLAN Management
98
+
99
+ Manage VLAN interfaces:
100
+
101
+ ```bash
102
+ # Create VLAN interface
103
+ curl -X POST "http://127.0.0.1:8000/vlan" \
104
+ -H "access_token: your_zpod_password" \
105
+ -H "Content-Type: application/json" \
106
+ -d '{"vlan": 2000, "gateway": "192.168.42.129/25"}'
107
+
108
+ # List all VLAN interfaces
109
+ curl -X GET "http://127.0.0.1:8000/vlan" \
110
+ -H "access_token: your_zpod_password"
111
+ ```
112
+
113
+ For complete VLAN management documentation, see [DOC_VLAN.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_VLAN.md).
114
+
115
+
116
+ ## Development
117
+
118
+ The project is managed with [uv](https://docs.astral.sh/uv/). Clone the repository, then:
119
+
120
+ ```bash
121
+ uv sync # create .venv with the project and dev dependencies
122
+ uv run pytest # run the unit tests (no root, /etc or network access needed)
123
+ uv run pytest --cov # same, with a coverage report
124
+ uv run ruff check src tests && uv run ruff format --check src tests
125
+ ```
126
+
127
+ A `justfile` wraps the same commands (`just test`, `just lint`, `just format`).
128
+
129
+ ### Releasing
130
+
131
+ Every change gets a line under `[Unreleased]` in [CHANGELOG.md](https://github.com/zPodFactory/zboxapi/blob/main/CHANGELOG.md). A release is
132
+ one command:
133
+
134
+ ```bash
135
+ python3 tools/release.py 0.2.0 --push # or: just release 0.2.0
136
+ ```
137
+
138
+ It turns `[Unreleased]` into a dated `[0.2.0]` section, bumps `pyproject.toml` and `uv.lock`,
139
+ runs the tests, commits, tags `v0.2.0` and pushes. The tag then runs
140
+ `.github/workflows/release.yml`, which publishes the changelog section as the GitHub release
141
+ note, builds the package with `uv build`, publishes it to PyPI with `uv publish` and attaches
142
+ the wheel and sdist to the release. See [tools/README.md](https://github.com/zPodFactory/zboxapi/blob/main/tools/README.md) for the details,
143
+ including the one-time PyPI setup (an API token secret or trusted publishing).
144
+
145
+ The test suite exercises every endpoint through FastAPI's `TestClient`. The hosts file,
146
+ `/etc/zboxapi.conf`, `/etc/network/interfaces.d/` and the `vmtoolsd` password lookup are
147
+ redirected to temporary locations, and the `ip`, `ifup`, `ifdown` and `pkill` commands are
148
+ replaced by an in-memory fake, so the tests can run on any machine.
149
+
150
+ ## Documentation
151
+
152
+ - [DOC_DNS.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_DNS.md) - Complete guide to DNS management features
153
+ - [DOC_VLAN.md](https://github.com/zPodFactory/zboxapi/blob/main/DOC_VLAN.md) - Complete guide to VLAN management features
@@ -0,0 +1,75 @@
1
+ [project]
2
+ name = "zboxapi"
3
+ version = "0.1.1"
4
+ description = "zPodFactory zBox API: DNS and VLAN management for the zbox VM"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.14"
8
+ classifiers = [
9
+ "Programming Language :: Python :: 3",
10
+ "Programming Language :: Python :: 3.14",
11
+ "Framework :: FastAPI",
12
+ "Operating System :: POSIX :: Linux",
13
+ ]
14
+ dependencies = [
15
+ "fastapi>=0.118",
16
+ "pydantic>=2.12",
17
+ "uvicorn>=0.37",
18
+ ]
19
+
20
+ [[project.authors]]
21
+ name = "Kelby Valenti"
22
+ email = "kelby.valenti@gmail.com"
23
+
24
+ [[project.authors]]
25
+ name = "Timo Sugliani"
26
+ email = "timo.sugliani@gmail.com"
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/zPodFactory/zboxapi"
30
+ Repository = "https://github.com/zPodFactory/zboxapi"
31
+ Changelog = "https://github.com/zPodFactory/zboxapi/blob/main/CHANGELOG.md"
32
+ Documentation = "https://github.com/zPodFactory/zboxapi#readme"
33
+ Issues = "https://github.com/zPodFactory/zboxapi/issues"
34
+
35
+ [project.scripts]
36
+ zboxapi = "zboxapi.main:launch"
37
+
38
+ [dependency-groups]
39
+ dev = [
40
+ "httpx2>=0.1",
41
+ "ipython>=8.24",
42
+ "pytest>=8.3",
43
+ "pytest-cov>=6.0",
44
+ "ruff>=0.13",
45
+ ]
46
+
47
+ [build-system]
48
+ requires = ["uv_build>=0.9,<1"]
49
+ build-backend = "uv_build"
50
+
51
+ [tool.uv]
52
+ required-version = ">=0.9"
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ addopts = "-ra"
57
+
58
+ [tool.coverage.run]
59
+ source = ["zboxapi"]
60
+
61
+ [tool.ruff]
62
+ target-version = "py314"
63
+ line-length = 88
64
+
65
+ [tool.ruff.lint]
66
+ select = [
67
+ "E",
68
+ "W",
69
+ "F",
70
+ "I",
71
+ "C",
72
+ "B",
73
+ "UP",
74
+ ]
75
+ ignore = ["B008"]
@@ -0,0 +1,73 @@
1
+ [project]
2
+ name = "zboxapi"
3
+ version = "0.1.1"
4
+ description = "zPodFactory zBox API: DNS and VLAN management for the zbox VM"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.14"
8
+ authors = [
9
+ { name = "Kelby Valenti", email = "kelby.valenti@gmail.com" },
10
+ { name = "Timo Sugliani", email = "timo.sugliani@gmail.com" },
11
+ ]
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.14",
15
+ "Framework :: FastAPI",
16
+ "Operating System :: POSIX :: Linux",
17
+ ]
18
+ dependencies = [
19
+ "fastapi>=0.118",
20
+ "pydantic>=2.12",
21
+ "uvicorn>=0.37",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/zPodFactory/zboxapi"
26
+ Repository = "https://github.com/zPodFactory/zboxapi"
27
+ Changelog = "https://github.com/zPodFactory/zboxapi/blob/main/CHANGELOG.md"
28
+ Documentation = "https://github.com/zPodFactory/zboxapi#readme"
29
+ Issues = "https://github.com/zPodFactory/zboxapi/issues"
30
+
31
+ [project.scripts]
32
+ zboxapi = "zboxapi.main:launch"
33
+
34
+ [dependency-groups]
35
+ dev = [
36
+ "httpx2>=0.1",
37
+ "ipython>=8.24",
38
+ "pytest>=8.3",
39
+ "pytest-cov>=6.0",
40
+ "ruff>=0.13",
41
+ ]
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.9,<1"]
45
+ build-backend = "uv_build"
46
+
47
+ [tool.uv]
48
+ required-version = ">=0.9"
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
52
+ addopts = "-ra"
53
+
54
+ [tool.coverage.run]
55
+ source = ["zboxapi"]
56
+
57
+ [tool.ruff]
58
+ target-version = "py314"
59
+ line-length = 88
60
+
61
+ [tool.ruff.lint]
62
+ select = [
63
+ "E", # pycodestyle errors
64
+ "W", # pycodestyle warnings
65
+ "F", # pyflakes
66
+ "I", # isort
67
+ "C", # flake8-comprehensions
68
+ "B", # flake8-bugbear
69
+ "UP", # pyupgrade
70
+ ]
71
+ ignore = [
72
+ "B008",
73
+ ]
@@ -0,0 +1,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("zboxapi")
5
+ except PackageNotFoundError: # pragma: no cover - running from a bare checkout
6
+ __version__ = "0.0.0"
@@ -12,11 +12,14 @@ from fastapi import APIRouter, HTTPException, status
12
12
  from pydantic import AfterValidator, BaseModel
13
13
  from pydantic_core import PydanticCustomError
14
14
 
15
+ # Path of the hosts file managed by this API (module-level so tests can override)
16
+ HOSTS_FILE = Path("/etc/hosts")
17
+
15
18
 
16
19
  @contextlib.contextmanager
17
20
  def get_hosts_file_object():
18
- """Context manager for safely handling /etc/hosts file"""
19
- pfile = Path("/etc/hosts")
21
+ """Context manager for safely handling the hosts file"""
22
+ pfile = HOSTS_FILE
20
23
  if not pfile.is_file():
21
24
  pfile.write_text("")
22
25
 
@@ -25,7 +28,7 @@ def get_hosts_file_object():
25
28
  file_handle = pfile.open("r+")
26
29
  fcntl.flock(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
27
30
  break
28
- except IOError: # noqa: UP024
31
+ except OSError:
29
32
  # File is locked, wait for a while and try again
30
33
  time.sleep(0.1)
31
34
 
@@ -105,19 +108,37 @@ def RecordAlreadyPresent(ip, hostname):
105
108
 
106
109
 
107
110
  def validate_hostname(value: str):
108
- """Validate hostname format"""
109
- if not 1 <= len(value) < 64:
110
- raise PydanticCustomError("value_error", "Invalid hostname length")
111
-
112
- # Define pattern of DNS label
113
- # Can begin and end with a number or letter only
114
- # Can contain hyphens, a-z, A-Z, 0-9
115
- # 1 - 63 chars allowed
116
- hostname_re = re.compile(r"^[a-z0-9]([a-z-0-9-]{0,61}[a-z0-9])?$", re.IGNORECASE)
117
-
118
- # Check that all labels match that pattern.
119
- if not hostname_re.match(value):
120
- raise PydanticCustomError("value_error", "Invalid hostname")
111
+ """Validate hostname format according to DNS standards"""
112
+ # Check total FQDN length (1-253 characters)
113
+ if not 1 <= len(value) <= 253:
114
+ raise PydanticCustomError(
115
+ "value_error",
116
+ f"Invalid hostname length: {len(value)} characters (must be 1-253)",
117
+ )
118
+
119
+ # Define pattern for individual DNS labels
120
+ # Each label: 1-63 chars, alphanumeric + hyphens, can't start/end with hyphen
121
+ label_re = re.compile(r"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$", re.IGNORECASE)
122
+
123
+ # Check that all labels match the pattern and length requirements
124
+ for label in value.split("."):
125
+ if not label: # Empty label (leading/trailing/consecutive dots)
126
+ raise PydanticCustomError("value_error", "Invalid hostname: empty label")
127
+
128
+ # Check individual label length (1-63 characters)
129
+ if len(label) > 63:
130
+ raise PydanticCustomError(
131
+ "value_error",
132
+ f"Invalid label length: '{label}' is {len(label)} characters "
133
+ "(must be 1-63)",
134
+ )
135
+
136
+ # Check label format (alphanumeric + hyphens, can't start/end with hyphen)
137
+ if not label_re.match(label):
138
+ raise PydanticCustomError(
139
+ "value_error", f"Invalid hostname label: '{label}'"
140
+ )
141
+
121
142
  return value
122
143
 
123
144
 
@@ -1,12 +1,15 @@
1
+ import functools
1
2
  import os
2
3
  import re
3
4
  import subprocess
5
+ from collections.abc import AsyncIterator
6
+ from contextlib import asynccontextmanager
4
7
  from typing import Annotated
5
8
 
6
9
  import uvicorn
7
10
  from fastapi import Depends, FastAPI, HTTPException, Security, status
8
11
  from fastapi.routing import APIRoute
9
- from fastapi.security.api_key import APIKey, APIKeyHeader
12
+ from fastapi.security.api_key import APIKeyHeader
10
13
 
11
14
  from zboxapi import __version__
12
15
  from zboxapi.dns import dns_router
@@ -15,17 +18,9 @@ from zboxapi.vlan import vlan_router
15
18
  api_key_header = APIKeyHeader(name="access_token", auto_error=False)
16
19
 
17
20
 
18
- def validate_api_key(api_key: Annotated[APIKey, Security(api_key_header)]):
19
- """Validate API key for authentication"""
20
- if api_key != ZPOD_PASSWORD:
21
- raise HTTPException(
22
- status_code=status.HTTP_403_FORBIDDEN,
23
- detail="Invalid access_token",
24
- )
25
-
26
-
27
- def get_zpod_password():
28
- """Retrieve zpod password from VMware tools"""
21
+ @functools.cache
22
+ def get_zpod_password() -> str:
23
+ """Retrieve zpod password from VMware tools (cached after first lookup)"""
29
24
  ovfenv = subprocess.run(
30
25
  ["vmtoolsd", "--cmd", "info-get guestinfo.ovfenv"],
31
26
  capture_output=True,
@@ -37,19 +32,30 @@ def get_zpod_password():
37
32
  raise Exception("Unable to retrieve zpod password")
38
33
 
39
34
 
40
- def simplify_operation_ids(api: FastAPI) -> None:
35
+ def validate_api_key(api_key: Annotated[str | None, Security(api_key_header)]):
36
+ """Validate API key for authentication"""
37
+ if api_key != get_zpod_password():
38
+ raise HTTPException(
39
+ status_code=status.HTTP_403_FORBIDDEN,
40
+ detail="Invalid access_token",
41
+ )
42
+
43
+
44
+ def generate_operation_id(route: APIRoute) -> str:
41
45
  """
42
- Update operation IDs so that generated API clients have simpler function
43
- names.
46
+ Build operation IDs as "<tag>_<function name>" so that generated API
47
+ clients have simpler function names (e.g. dns_dns_get_all).
44
48
  """
45
- for route in api.routes:
46
- if isinstance(route, APIRoute) and not route.operation_id:
47
- tag = route.tags[0] if route.tags else "default"
48
- route.operation_id = f"{tag}_{route.name}"
49
+ tag = route.tags[0] if route.tags else "default"
50
+ return f"{tag}_{route.name}"
49
51
 
50
52
 
51
- # Get zpod password for authentication
52
- ZPOD_PASSWORD = get_zpod_password()
53
+ @asynccontextmanager
54
+ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
55
+ """Resolve the zpod password at startup so a broken VM env fails fast"""
56
+ get_zpod_password()
57
+ yield
58
+
53
59
 
54
60
  # Get root path from environment
55
61
  zboxapi_root_path = os.getenv("ZBOXAPI_ROOT_PATH", None)
@@ -60,15 +66,14 @@ app = FastAPI(
60
66
  root_path=zboxapi_root_path,
61
67
  dependencies=[Depends(validate_api_key)],
62
68
  version=__version__,
69
+ lifespan=lifespan,
70
+ generate_unique_id_function=generate_operation_id,
63
71
  )
64
72
 
65
73
  # Include routers
66
74
  app.include_router(dns_router)
67
75
  app.include_router(vlan_router)
68
76
 
69
- # Simplify operation IDs
70
- simplify_operation_ids(app)
71
-
72
77
 
73
78
  def launch():
74
79
  """Launch the FastAPI application with uvicorn"""
@@ -12,6 +12,10 @@ from fastapi import APIRouter, HTTPException, status
12
12
  from pydantic import AfterValidator, BaseModel, Field
13
13
  from pydantic_core import PydanticCustomError
14
14
 
15
+ # Paths managed by this API (module-level so tests can override)
16
+ CONFIG_FILE = Path("/etc/zboxapi.conf")
17
+ INTERFACES_DIR = Path("/etc/network/interfaces.d")
18
+
15
19
 
16
20
  class ConfigError(Exception):
17
21
  """Configuration error"""
@@ -28,22 +32,25 @@ class NetworkError(Exception):
28
32
  def load_config() -> configparser.ConfigParser:
29
33
  """Load configuration from /etc/zboxapi.conf"""
30
34
  config = configparser.ConfigParser()
31
- config_path = Path("/etc/zboxapi.conf")
35
+ config_path = CONFIG_FILE
32
36
 
33
37
  if not config_path.exists():
34
- raise ConfigError("Configuration file /etc/zboxapi.conf not found")
38
+ raise ConfigError(f"Configuration file {config_path} not found")
35
39
 
36
40
  config.read(config_path)
37
41
  return config
38
42
 
39
43
 
40
44
  def get_config_value(
41
- config: configparser.ConfigParser, section: str, key: str, default: str = None
45
+ config: configparser.ConfigParser,
46
+ section: str,
47
+ key: str,
48
+ default: str | None = None,
42
49
  ) -> str:
43
50
  """Get configuration value with optional default"""
44
51
  try:
45
52
  return config.get(section, key)
46
- except (configparser.NoSectionError, configparser.NoOptionError):
53
+ except configparser.NoSectionError, configparser.NoOptionError:
47
54
  if default is not None:
48
55
  return default
49
56
  raise ConfigError(f"Configuration missing: [{section}] {key}") from None
@@ -115,7 +122,7 @@ def validate_cidr(cidr: str) -> str:
115
122
  """Validate CIDR notation, but preserve the original input."""
116
123
  try:
117
124
  # Parse the CIDR to validate it's correct
118
- network = ipaddress.IPv4Network(cidr, strict=False)
125
+ ipaddress.IPv4Network(cidr, strict=False)
119
126
  # But return the original input, not the normalized network
120
127
  return cidr
121
128
  except ValueError as e:
@@ -150,7 +157,9 @@ def check_no_overlap(cidrs):
150
157
  return True
151
158
 
152
159
 
153
- def validate_vlan_networks(new_gateway: str, exclude_vlan_id: int = None) -> None:
160
+ def validate_vlan_networks(
161
+ new_gateway: str, exclude_vlan_id: int | None = None
162
+ ) -> None:
154
163
  """Validate that a new gateway doesn't overlap with existing VLAN networks"""
155
164
  # Get all existing VLAN gateways
156
165
  existing_vlans = get_existing_vlans()
@@ -160,6 +169,13 @@ def validate_vlan_networks(new_gateway: str, exclude_vlan_id: int = None) -> Non
160
169
  # Skip the VLAN we're updating (if this is an update operation)
161
170
  if exclude_vlan_id is not None and vlan.vlan == exclude_vlan_id:
162
171
  continue
172
+ # System VLANs whose interface has no address carry a placeholder
173
+ # ("system-default" / "system-zpod") instead of a CIDR: nothing to
174
+ # compare against, so leave them out of the overlap check.
175
+ try:
176
+ ipaddress.ip_network(vlan.gateway, strict=False)
177
+ except ValueError:
178
+ continue
163
179
  all_gateways.append(vlan.gateway)
164
180
 
165
181
  # Check for overlaps
@@ -192,9 +208,9 @@ class VlanView(BaseModel):
192
208
 
193
209
  @contextlib.contextmanager
194
210
  def get_vlan_config_file_object(vlan_id: int):
195
- """Context manager for safely handling VLAN configuration files in /etc/network/interfaces.d/"""
211
+ """Context manager for safely handling VLAN config files in interfaces.d/"""
196
212
  interface_name = get_interface_name()
197
- config_dir = Path("/etc/network/interfaces.d")
213
+ config_dir = INTERFACES_DIR
198
214
  config_file = config_dir / f"{interface_name}.{vlan_id}.cfg"
199
215
 
200
216
  # Ensure the directory exists
@@ -205,7 +221,7 @@ def get_vlan_config_file_object(vlan_id: int):
205
221
  file_handle = config_file.open("w") # Use write mode for individual files
206
222
  fcntl.flock(file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
207
223
  break
208
- except IOError:
224
+ except OSError:
209
225
  time.sleep(0.1)
210
226
 
211
227
  try:
@@ -215,108 +231,95 @@ def get_vlan_config_file_object(vlan_id: int):
215
231
  file_handle.close()
216
232
 
217
233
 
218
- def get_existing_vlans() -> list[VlanView]:
219
- """Get all existing VLAN configurations from /etc/network/interfaces.d/ and system VLANs"""
220
- interface_name = get_interface_name()
221
- config_dir = Path("/etc/network/interfaces.d")
234
+ def get_interface_status(interface_name_full: str) -> str:
235
+ """Return up if the interface exists and is up, otherwise down"""
236
+ try:
237
+ result = subprocess.run(
238
+ ["ip", "link", "show", interface_name_full],
239
+ capture_output=True,
240
+ text=True,
241
+ check=False,
242
+ )
243
+ if result.returncode == 0 and "UP" in result.stdout:
244
+ return "up"
245
+ except Exception:
246
+ pass
247
+ return "down"
222
248
 
223
- vlans = []
224
249
 
225
- # Add system default VLANs
226
- system_vlans_default = get_system_vlans_default()
227
- for vlan_id in system_vlans_default:
228
- # Try to get actual gateway from ip addr command
229
- gateway = "system-default"
230
- interface_name_full = f"{interface_name}.{vlan_id}"
250
+ def get_interface_gateway(interface_name_full: str) -> str | None:
251
+ """Return the first inet address (CIDR) of an interface, if any"""
252
+ try:
253
+ result = subprocess.run(
254
+ ["ip", "addr", "show", interface_name_full],
255
+ capture_output=True,
256
+ text=True,
257
+ check=False,
258
+ )
259
+ if result.returncode == 0:
260
+ ip_match = re.search(r"inet\s+(\d+\.\d+\.\d+\.\d+/\d+)", result.stdout)
261
+ if ip_match:
262
+ return ip_match.group(1)
263
+ except Exception:
264
+ pass
265
+ return None
231
266
 
232
- # Check if interface is currently up
233
- status = "down"
234
- try:
235
- result = subprocess.run(
236
- ["ip", "link", "show", interface_name_full],
237
- capture_output=True,
238
- text=True,
239
- check=False,
240
- )
241
- if result.returncode == 0 and "UP" in result.stdout:
242
- status = "up"
243
- except Exception:
244
- pass
245
267
 
246
- try:
247
- result = subprocess.run(
248
- ["ip", "addr", "show", interface_name_full],
249
- capture_output=True,
250
- text=True,
251
- check=False,
252
- )
253
- if result.returncode == 0:
254
- # Extract IP address from ip addr output
255
- ip_match = re.search(r"inet\s+(\d+\.\d+\.\d+\.\d+/\d+)", result.stdout)
256
- if ip_match:
257
- gateway = ip_match.group(1)
258
- except Exception:
259
- pass
260
-
261
- vlans.append(
262
- VlanView(
263
- vlan=vlan_id,
264
- gateway=gateway,
265
- interface=interface_name_full,
266
- status=status,
267
- owner="system-default",
268
- )
269
- )
268
+ def get_system_vlan_view(interface_name: str, vlan_id: int, owner: str) -> VlanView:
269
+ """Build the view of a system VLAN from the live interface state"""
270
+ interface_name_full = f"{interface_name}.{vlan_id}"
271
+ return VlanView(
272
+ vlan=vlan_id,
273
+ gateway=get_interface_gateway(interface_name_full) or owner,
274
+ interface=interface_name_full,
275
+ status=get_interface_status(interface_name_full),
276
+ owner=owner,
277
+ )
270
278
 
271
- # Add system zPod VLANs
272
- system_vlans_zpod = get_system_vlans_zpod()
273
- for vlan_id in system_vlans_zpod:
274
- # Try to get actual gateway from ip addr command
275
- gateway = "system-zpod"
276
- interface_name_full = f"{interface_name}.{vlan_id}"
277
279
 
278
- # Check if interface is currently up
279
- status = "down"
280
- try:
281
- result = subprocess.run(
282
- ["ip", "link", "show", interface_name_full],
283
- capture_output=True,
284
- text=True,
285
- check=False,
286
- )
287
- if result.returncode == 0 and "UP" in result.stdout:
288
- status = "up"
289
- except Exception:
290
- pass
280
+ def get_user_vlan_view(
281
+ interface_name: str, vlan_id: int, config_file: Path
282
+ ) -> VlanView | None:
283
+ """Build the view of a user-defined VLAN from its interfaces.d config file"""
284
+ try:
285
+ content = config_file.read_text()
286
+ except OSError:
287
+ return None
291
288
 
292
- try:
293
- result = subprocess.run(
294
- ["ip", "addr", "show", interface_name_full],
295
- capture_output=True,
296
- text=True,
297
- check=False,
298
- )
299
- if result.returncode == 0:
300
- # Extract IP address from ip addr output
301
- ip_match = re.search(r"inet\s+(\d+\.\d+\.\d+\.\d+/\d+)", result.stdout)
302
- if ip_match:
303
- gateway = ip_match.group(1)
304
- except Exception:
305
- pass
306
-
307
- vlans.append(
308
- VlanView(
309
- vlan=vlan_id,
310
- gateway=gateway,
311
- interface=interface_name_full,
312
- status=status,
313
- owner="system-zpod",
314
- )
315
- )
289
+ # Extract gateway from the configuration
290
+ gateway_match = re.search(r"address\s+(\d+\.\d+\.\d+\.\d+/\d+)", content)
291
+ if not gateway_match:
292
+ return None
293
+
294
+ interface_name_full = f"{interface_name}.{vlan_id}"
295
+ return VlanView(
296
+ vlan=vlan_id,
297
+ gateway=gateway_match.group(1),
298
+ interface=interface_name_full,
299
+ status=get_interface_status(interface_name_full),
300
+ owner="user-defined",
301
+ )
316
302
 
317
- # Add user-configured VLANs from /etc/network/interfaces.d/
303
+
304
+ def get_existing_vlans() -> list[VlanView]:
305
+ """Get all VLANs: system VLANs from config plus user VLANs from interfaces.d/"""
306
+ interface_name = get_interface_name()
307
+ config_dir = INTERFACES_DIR
308
+
309
+ system_vlans_default = get_system_vlans_default()
310
+ system_vlans_zpod = get_system_vlans_zpod()
311
+
312
+ vlans = [
313
+ get_system_vlan_view(interface_name, vlan_id, "system-default")
314
+ for vlan_id in system_vlans_default
315
+ ]
316
+ vlans.extend(
317
+ get_system_vlan_view(interface_name, vlan_id, "system-zpod")
318
+ for vlan_id in system_vlans_zpod
319
+ )
320
+
321
+ # Add user-configured VLANs from interfaces.d/
318
322
  if config_dir.exists():
319
- # Find all VLAN configuration files
320
323
  vlan_pattern = rf"{re.escape(interface_name)}\.(\d+)\.cfg$"
321
324
 
322
325
  for config_file in config_dir.glob(f"{interface_name}.*.cfg"):
@@ -330,44 +333,8 @@ def get_existing_vlans() -> list[VlanView]:
330
333
  if vlan_id in system_vlans_default or vlan_id in system_vlans_zpod:
331
334
  continue
332
335
 
333
- try:
334
- with open(config_file, "r") as f:
335
- content = f.read()
336
-
337
- # Extract gateway from the configuration
338
- gateway_match = re.search(
339
- r"address\s+(\d+\.\d+\.\d+\.\d+/\d+)", content
340
- )
341
- if not gateway_match:
342
- continue
343
-
344
- gateway = gateway_match.group(1)
345
-
346
- # Check if interface is currently up
347
- status = "down"
348
- try:
349
- result = subprocess.run(
350
- ["ip", "link", "show", f"{interface_name}.{vlan_id}"],
351
- capture_output=True,
352
- text=True,
353
- check=False,
354
- )
355
- if result.returncode == 0 and "UP" in result.stdout:
356
- status = "up"
357
- except Exception:
358
- pass
359
-
360
- vlans.append(
361
- VlanView(
362
- vlan=vlan_id,
363
- gateway=gateway,
364
- interface=f"{interface_name}.{vlan_id}",
365
- status=status,
366
- owner="user-defined",
367
- )
368
- )
369
- except Exception:
370
- continue # Skip files that can't be read or parsed
336
+ if view := get_user_vlan_view(interface_name, vlan_id, config_file):
337
+ vlans.append(view)
371
338
 
372
339
  return sorted(vlans, key=lambda x: x.vlan)
373
340
 
@@ -381,8 +348,7 @@ def add_vlan_interface(vlan_id: int, gateway: str) -> None:
381
348
  validate_vlan_networks(gateway)
382
349
 
383
350
  # Check if VLAN configuration file already exists
384
- config_dir = Path("/etc/network/interfaces.d")
385
- config_file = config_dir / f"{interface_name}.{vlan_id}.cfg"
351
+ config_file = INTERFACES_DIR / f"{interface_name}.{vlan_id}.cfg"
386
352
 
387
353
  if config_file.exists():
388
354
  raise NetworkError(f"VLAN interface {interface_name}.{vlan_id} already exists")
@@ -404,12 +370,11 @@ def update_vlan_interface(vlan_id: int, gateway: str) -> None:
404
370
  interface_name = get_interface_name()
405
371
  mtu = get_mtu()
406
372
 
407
- # Validate inputs - check for overlaps and gateway uniqueness, excluding current VLAN
373
+ # Validate inputs - check for overlaps, excluding the VLAN being updated
408
374
  validate_vlan_networks(gateway, exclude_vlan_id=vlan_id)
409
375
 
410
376
  # Check if VLAN configuration file exists
411
- config_dir = Path("/etc/network/interfaces.d")
412
- config_file = config_dir / f"{interface_name}.{vlan_id}.cfg"
377
+ config_file = INTERFACES_DIR / f"{interface_name}.{vlan_id}.cfg"
413
378
 
414
379
  if not config_file.exists():
415
380
  raise NetworkError(f"VLAN interface {interface_name}.{vlan_id} does not exist")
@@ -429,8 +394,7 @@ iface {interface_name}.{vlan_id} inet static
429
394
  def delete_vlan_interface(vlan_id: int) -> None:
430
395
  """Delete VLAN interface configuration from /etc/network/interfaces.d/"""
431
396
  interface_name = get_interface_name()
432
- config_dir = Path("/etc/network/interfaces.d")
433
- config_file = config_dir / f"{interface_name}.{vlan_id}.cfg"
397
+ config_file = INTERFACES_DIR / f"{interface_name}.{vlan_id}.cfg"
434
398
 
435
399
  if not config_file.exists():
436
400
  raise NetworkError(f"VLAN interface {interface_name}.{vlan_id} does not exist")
@@ -572,6 +536,9 @@ def vlan_update(vlan_id: int, vlan_in: VlanUpdate) -> VlanView:
572
536
  status="up",
573
537
  owner="user-defined",
574
538
  )
539
+ except PydanticCustomError as e:
540
+ # System VLANs are forbidden from modification
541
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e)) from e
575
542
  except (ConfigError, NetworkError) as e:
576
543
  raise HTTPException(
577
544
  status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)
@@ -590,10 +557,13 @@ def vlan_delete(vlan_id: int) -> dict:
590
557
  # Validate VLAN ID
591
558
  validate_vlan_id(vlan_id)
592
559
 
593
- # Delete VLAN interface configuration (includes bringing down and deleting interface)
560
+ # Delete VLAN configuration (brings the interface down first)
594
561
  delete_vlan_interface(vlan_id)
595
562
 
596
563
  return {"message": f"VLAN {vlan_id} deleted successfully"}
564
+ except PydanticCustomError as e:
565
+ # System VLANs are forbidden from modification
566
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=str(e)) from e
597
567
  except (ConfigError, NetworkError) as e:
598
568
  raise HTTPException(
599
569
  status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)
zboxapi-0.0.7/PKG-INFO DELETED
@@ -1,135 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: zboxapi
3
- Version: 0.0.7
4
- Summary:
5
- Author: Kelby Valenti
6
- Author-email: kelby.valenti@gmail.com
7
- Requires-Python: >=3.10
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.10
10
- Classifier: Programming Language :: Python :: 3.11
11
- Classifier: Programming Language :: Python :: 3.12
12
- Requires-Dist: fastapi (==0.111.0)
13
- Requires-Dist: ipython (>=8.24.0,<9.0.0)
14
- Requires-Dist: uvicorn (==0.29.0)
15
- Description-Content-Type: text/markdown
16
-
17
- # zBoxApi
18
-
19
- zPodFactory zBox Api
20
-
21
- ## Features
22
-
23
- - **DNS Management**: Manage DNS records in `/etc/hosts` with automatic dnsmasq integration
24
- - **VLAN Management**: Manage VLAN interfaces with automatic network configuration
25
-
26
- ## Installation
27
-
28
- Complete the following steps to set up zBox Api:
29
-
30
- 1. Install pipx
31
-
32
- ```bash
33
- # Install and configure pipx
34
- apt update
35
- apt install -y pipx
36
- pipx ensurepath
37
-
38
- # Reload your profile
39
- source ~/.zshrc
40
- ```
41
-
42
- 1. Install zBoxApi:
43
-
44
- ```bash
45
- pipx install zboxapi
46
- ```
47
-
48
- 1. Set up and start zboxapi.service
49
-
50
- ```bash
51
- cp zboxapi.service /etc/systemd/system
52
- systemctl daemon-reload
53
- systemctl enable zboxapi.service
54
- systemctl start zboxapi.service
55
- ```
56
-
57
- **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
58
-
59
- ## Configuration
60
-
61
- ### VLAN Management
62
-
63
- For VLAN management functionality, create a configuration file at `/etc/zboxapi.conf`:
64
-
65
- ```ini
66
- [DEFAULT]
67
- # Base interface name for VLAN management
68
- interface = eth1
69
-
70
- # MTU setting for VLAN interfaces
71
- mtu = 1700
72
-
73
- # System default VLANs that cannot be modified (comma-separated)
74
- system_vlans_default = 10,20,30
75
-
76
- # System zPod VLANs that cannot be modified (comma-separated)
77
- system_vlans_zpod = 64,128,192
78
- ```
79
-
80
- See [DOC_VLAN.md](DOC_VLAN.md) for detailed documentation on VLAN management features.
81
-
82
- ## API Usage
83
-
84
- ### Authentication
85
-
86
- All API endpoints require authentication using the `access_token` header. The API key is the zPod password which is also the root password of the zbox VM. The password is automatically retrieved from VMware tools:
87
-
88
- ```bash
89
- curl -H "access_token: your_zpod_password" http://127.0.0.1:8000/dns
90
- ```
91
-
92
- **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
93
-
94
- ### DNS Management
95
-
96
- Manage DNS records in `/etc/hosts`:
97
-
98
- ```bash
99
- # Add DNS record
100
- curl -X POST "http://127.0.0.1:8000/dns" \
101
- -H "access_token: your_zpod_password" \
102
- -H "Content-Type: application/json" \
103
- -d '{"ip": "192.168.1.100", "hostname": "example.com"}'
104
-
105
- # List all DNS records
106
- curl -X GET "http://127.0.0.1:8000/dns" \
107
- -H "access_token: your_zpod_password"
108
- ```
109
-
110
- For complete DNS management documentation, see [DOC_DNS.md](DOC_DNS.md).
111
-
112
- ### VLAN Management
113
-
114
- Manage VLAN interfaces:
115
-
116
- ```bash
117
- # Create VLAN interface
118
- curl -X POST "http://127.0.0.1:8000/vlan" \
119
- -H "access_token: your_zpod_password" \
120
- -H "Content-Type: application/json" \
121
- -d '{"vlan": 2000, "gateway": "192.168.42.129/25"}'
122
-
123
- # List all VLAN interfaces
124
- curl -X GET "http://127.0.0.1:8000/vlan" \
125
- -H "access_token: your_zpod_password"
126
- ```
127
-
128
- For complete VLAN management documentation, see [DOC_VLAN.md](DOC_VLAN.md).
129
-
130
-
131
- ## Documentation
132
-
133
- - [DOC_DNS.md](DOC_DNS.md) - Complete guide to DNS management features
134
- - [DOC_VLAN.md](DOC_VLAN.md) - Complete guide to VLAN management features
135
-
zboxapi-0.0.7/README.md DELETED
@@ -1,118 +0,0 @@
1
- # zBoxApi
2
-
3
- zPodFactory zBox Api
4
-
5
- ## Features
6
-
7
- - **DNS Management**: Manage DNS records in `/etc/hosts` with automatic dnsmasq integration
8
- - **VLAN Management**: Manage VLAN interfaces with automatic network configuration
9
-
10
- ## Installation
11
-
12
- Complete the following steps to set up zBox Api:
13
-
14
- 1. Install pipx
15
-
16
- ```bash
17
- # Install and configure pipx
18
- apt update
19
- apt install -y pipx
20
- pipx ensurepath
21
-
22
- # Reload your profile
23
- source ~/.zshrc
24
- ```
25
-
26
- 1. Install zBoxApi:
27
-
28
- ```bash
29
- pipx install zboxapi
30
- ```
31
-
32
- 1. Set up and start zboxapi.service
33
-
34
- ```bash
35
- cp zboxapi.service /etc/systemd/system
36
- systemctl daemon-reload
37
- systemctl enable zboxapi.service
38
- systemctl start zboxapi.service
39
- ```
40
-
41
- **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
42
-
43
- ## Configuration
44
-
45
- ### VLAN Management
46
-
47
- For VLAN management functionality, create a configuration file at `/etc/zboxapi.conf`:
48
-
49
- ```ini
50
- [DEFAULT]
51
- # Base interface name for VLAN management
52
- interface = eth1
53
-
54
- # MTU setting for VLAN interfaces
55
- mtu = 1700
56
-
57
- # System default VLANs that cannot be modified (comma-separated)
58
- system_vlans_default = 10,20,30
59
-
60
- # System zPod VLANs that cannot be modified (comma-separated)
61
- system_vlans_zpod = 64,128,192
62
- ```
63
-
64
- See [DOC_VLAN.md](DOC_VLAN.md) for detailed documentation on VLAN management features.
65
-
66
- ## API Usage
67
-
68
- ### Authentication
69
-
70
- All API endpoints require authentication using the `access_token` header. The API key is the zPod password which is also the root password of the zbox VM. The password is automatically retrieved from VMware tools:
71
-
72
- ```bash
73
- curl -H "access_token: your_zpod_password" http://127.0.0.1:8000/dns
74
- ```
75
-
76
- **Note**: The service runs on `127.0.0.1:8000` and requires root privileges for network configuration operations.
77
-
78
- ### DNS Management
79
-
80
- Manage DNS records in `/etc/hosts`:
81
-
82
- ```bash
83
- # Add DNS record
84
- curl -X POST "http://127.0.0.1:8000/dns" \
85
- -H "access_token: your_zpod_password" \
86
- -H "Content-Type: application/json" \
87
- -d '{"ip": "192.168.1.100", "hostname": "example.com"}'
88
-
89
- # List all DNS records
90
- curl -X GET "http://127.0.0.1:8000/dns" \
91
- -H "access_token: your_zpod_password"
92
- ```
93
-
94
- For complete DNS management documentation, see [DOC_DNS.md](DOC_DNS.md).
95
-
96
- ### VLAN Management
97
-
98
- Manage VLAN interfaces:
99
-
100
- ```bash
101
- # Create VLAN interface
102
- curl -X POST "http://127.0.0.1:8000/vlan" \
103
- -H "access_token: your_zpod_password" \
104
- -H "Content-Type: application/json" \
105
- -d '{"vlan": 2000, "gateway": "192.168.42.129/25"}'
106
-
107
- # List all VLAN interfaces
108
- curl -X GET "http://127.0.0.1:8000/vlan" \
109
- -H "access_token: your_zpod_password"
110
- ```
111
-
112
- For complete VLAN management documentation, see [DOC_VLAN.md](DOC_VLAN.md).
113
-
114
-
115
- ## Documentation
116
-
117
- - [DOC_DNS.md](DOC_DNS.md) - Complete guide to DNS management features
118
- - [DOC_VLAN.md](DOC_VLAN.md) - Complete guide to VLAN management features
@@ -1,43 +0,0 @@
1
- [tool.poetry]
2
- name = "zboxapi"
3
- version = "0.0.7"
4
- description = ""
5
- authors = ["Kelby Valenti <kelby.valenti@gmail.com>", "Timo Sugliani <timo.sugliani@gmail.com>"]
6
- readme = "README.md"
7
-
8
- [tool.poetry.scripts]
9
- zboxapi = "zboxapi.main:launch"
10
-
11
- [tool.poetry.dependencies]
12
- fastapi = "0.111.0"
13
- python = ">=3.10"
14
- uvicorn = "0.29.0"
15
- ipython = "^8.24.0"
16
-
17
- [tool.poetry.group.dev.dependencies]
18
- ruff = "^0.4"
19
-
20
- [build-system]
21
- requires = ["poetry-core"]
22
- build-backend = "poetry.core.masonry.api"
23
-
24
- [tool.ruff.lint]
25
- select = [
26
- "E", # pycodestyle errors
27
- "W", # pycodestyle warnings
28
- "F", # pyflakes
29
- "I", # isort
30
- "C", # flake8-comprehensions
31
- "B", # flake8-bugbear
32
- "UP", # pyupgrade
33
- ]
34
- ignore = [
35
- "B008",
36
- ]
37
-
38
- [[tool.poetry_bumpversion.replacements]]
39
- files = [
40
- "src/zboxapi/__init__.py",
41
- ]
42
- search = '__version__ = "{current_version}"'
43
- replace = '__version__ = "{new_version}"'
@@ -1 +0,0 @@
1
- __version__ = "0.0.7"