labops 0.3.2__tar.gz → 0.4.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.
- labops-0.4.0/.release-please-manifest.json +3 -0
- {labops-0.3.2 → labops-0.4.0}/CHANGELOG.md +20 -0
- {labops-0.3.2 → labops-0.4.0}/PKG-INFO +1 -1
- {labops-0.3.2 → labops-0.4.0}/justfile +14 -1
- labops-0.4.0/models/input_conf/common_validators/web_services.py +34 -0
- {labops-0.3.2 → labops-0.4.0}/models/input_conf/creds.py +3 -0
- labops-0.4.0/models/input_conf/custom_types.py +5 -0
- labops-0.4.0/models/input_conf/docker.py +12 -0
- labops-0.4.0/models/input_conf/host.py +69 -0
- labops-0.4.0/models/input_conf/lxc.py +25 -0
- labops-0.4.0/models/input_conf/vm.py +30 -0
- labops-0.4.0/models/input_conf/web_services.py +12 -0
- labops-0.4.0/models/input_conf/yaml_root.py +120 -0
- {labops-0.3.2 → labops-0.4.0}/pyproject.toml +1 -1
- {labops-0.3.2 → labops-0.4.0}/src/cli/host.py +1 -1
- {labops-0.3.2 → labops-0.4.0}/src/cli/lxc.py +2 -1
- {labops-0.3.2 → labops-0.4.0}/src/cli/validate.py +2 -2
- {labops-0.3.2 → labops-0.4.0}/src/cli/vm.py +1 -1
- {labops-0.3.2 → labops-0.4.0}/src/host/find.py +1 -1
- {labops-0.3.2 → labops-0.4.0}/src/host/setup.py +1 -1
- {labops-0.3.2 → labops-0.4.0}/src/host/update.py +2 -2
- {labops-0.3.2 → labops-0.4.0}/src/lxc/find.py +2 -1
- {labops-0.3.2 → labops-0.4.0}/src/lxc/update.py +2 -1
- labops-0.4.0/src/utils/yaml_validator.py +55 -0
- {labops-0.3.2 → labops-0.4.0}/src/vm/find.py +6 -6
- labops-0.4.0/test-samples/homelab-complete.yml +105 -0
- {labops-0.3.2 → labops-0.4.0}/uv.lock +1 -1
- labops-0.3.2/.release-please-manifest.json +0 -3
- labops-0.3.2/models/input_conf/hosts.py +0 -61
- labops-0.3.2/models/input_conf/yaml_root.py +0 -38
- labops-0.3.2/src/utils/yaml_validator.py +0 -24
- labops-0.3.2/test-samples/homelab-complete.yml +0 -100
- {labops-0.3.2 → labops-0.4.0}/.devcontainer/devcontainer.json +0 -0
- {labops-0.3.2 → labops-0.4.0}/.github/dependabot.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/.github/workflows/publish.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/.github/workflows/release_please.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/.gitignore +0 -0
- {labops-0.3.2 → labops-0.4.0}/.pre-commit-config.yaml +0 -0
- {labops-0.3.2 → labops-0.4.0}/.python-version +0 -0
- {labops-0.3.2 → labops-0.4.0}/LICENSE +0 -0
- {labops-0.3.2 → labops-0.4.0}/README.md +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/setup/alpine.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/setup/common.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/setup/debian.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/setup/redhat.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/setup.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/update/alpine.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/update/debian.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/update/redhat.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/host/update.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/lxc/update/alpine.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/lxc/update/debian.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/lxc/update/redhat.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/playbooks/lxc/update.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/ansible/requirements.yml +0 -0
- {labops-0.3.2 → labops-0.4.0}/img/Cover.png +0 -0
- {labops-0.3.2 → labops-0.4.0}/labops_cli.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/models/input_conf/settings.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/release-please-config.json +0 -0
- {labops-0.3.2 → labops-0.4.0}/src/cli/core.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/src/host/__init__.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/src/lxc/__init__.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/src/utils/__init__.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/src/utils/ansible_runner.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/src/vm/__init__.py +0 -0
- {labops-0.3.2 → labops-0.4.0}/test-samples/docker/homeassistant/docker-compose.yaml +0 -0
- {labops-0.3.2 → labops-0.4.0}/test-samples/docker/nebula-sync/docker-compose.yaml +0 -0
- {labops-0.3.2 → labops-0.4.0}/test-samples/docker/nginx/docker-compose.yaml +0 -0
- {labops-0.3.2 → labops-0.4.0}/test-samples/homelab-test.yml +0 -0
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0](https://github.com/FreezeManny/labops/compare/labops-v0.3.2...labops-v0.4.0) (2026-04-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add devcontainer commands and initialize docker attribute in Host model ([0838b09](https://github.com/FreezeManny/labops/commit/0838b099ecae1f0c51ac21b469234c561fae02a2))
|
|
9
|
+
* add validation for duplicate web service ports in host, lxc, and vm models ([683dd75](https://github.com/FreezeManny/labops/commit/683dd7537fb2028064316f19137b243bdf4c202a))
|
|
10
|
+
* added duplicate VMID Validation for proxmox host and lxc ([cb6d35a](https://github.com/FreezeManny/labops/commit/cb6d35ad07cafd7843c56c42cde98380600e0e41))
|
|
11
|
+
* added proxy name duplication check ([c68baf6](https://github.com/FreezeManny/labops/commit/c68baf6d4bbc90ab4d217784fb324c16d3acb33e))
|
|
12
|
+
* changed docker and webservice structure ([10a6da9](https://github.com/FreezeManny/labops/commit/10a6da94e1d6ddf0a6ba432243ee17412ed4af76))
|
|
13
|
+
* Changed LXC Structure ([95b9615](https://github.com/FreezeManny/labops/commit/95b9615dc99e9747ccff7b2be2c2b8c0833c74c9))
|
|
14
|
+
* enhance YAML validation by adding unique IP address check ([f9ecfdf](https://github.com/FreezeManny/labops/commit/f9ecfdf390b501832ec73bed8c2e761c2ce21662))
|
|
15
|
+
* Improve error handling for duplicate ports, VM IDs, and IP addresses in validators ([5fb8aa4](https://github.com/FreezeManny/labops/commit/5fb8aa427e57aeb56afc1bc754a5e41fd43679ba))
|
|
16
|
+
* Pretty output for one error ([6a570f0](https://github.com/FreezeManny/labops/commit/6a570f02d58ce9f0d0736968e8b0f1b3ce1e3166))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* fixed validate ([e8d229f](https://github.com/FreezeManny/labops/commit/e8d229fa831bf3611f4680ce8205658b68adbf27))
|
|
22
|
+
|
|
3
23
|
## [0.3.2](https://github.com/FreezeManny/labops/compare/labops-v0.3.1...labops-v0.3.2) (2026-04-21)
|
|
4
24
|
|
|
5
25
|
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
runner := "uv run labops_cli.py"
|
|
2
2
|
system := "debian"
|
|
3
|
-
test_conf := "./test-samples/homelab-
|
|
3
|
+
test_conf := "./test-samples/homelab-complete.yml"
|
|
4
4
|
|
|
5
5
|
test-args := ""
|
|
6
6
|
# In normal use no --file is needed — the CLI walks up from cwd and finds
|
|
7
7
|
# homelab.yml automatically, just like `docker compose` finds compose.yml.
|
|
8
8
|
# Test recipes pass --file explicitly to point at the sample config.
|
|
9
9
|
|
|
10
|
+
# ----------- DEVCONTAINER ------
|
|
11
|
+
container-start:
|
|
12
|
+
devcontainer up --workspace-folder .
|
|
13
|
+
|
|
14
|
+
container-attach:
|
|
15
|
+
devcontainer exec --workspace-folder . bash
|
|
16
|
+
|
|
10
17
|
pre-commit:
|
|
11
18
|
uv run pre-commit run --all-files
|
|
12
19
|
|
|
20
|
+
build:
|
|
21
|
+
uv build
|
|
22
|
+
|
|
23
|
+
local-install:
|
|
24
|
+
pipx install --force $(ls -t dist/*.whl | head -n1) --force
|
|
25
|
+
|
|
13
26
|
# ── Normal usage (auto-discovers homelab.yml from cwd) ────────────────────────
|
|
14
27
|
|
|
15
28
|
validate:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import TypeVar, Any
|
|
2
|
+
from ..web_services import WebService
|
|
3
|
+
from ..docker import Docker
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_duplicate_ws_ports(obj: T) -> T:
|
|
9
|
+
all_ports: set[int] = set()
|
|
10
|
+
errors: list[str] = []
|
|
11
|
+
|
|
12
|
+
web_services: WebService | None = getattr(obj, "web_services", None)
|
|
13
|
+
if web_services:
|
|
14
|
+
for ws in web_services.root:
|
|
15
|
+
if ws.port in all_ports:
|
|
16
|
+
errors.append(f"Duplicate port found: {ws.port}")
|
|
17
|
+
else:
|
|
18
|
+
all_ports.add(ws.port)
|
|
19
|
+
|
|
20
|
+
docker: Docker | None = getattr(obj, "docker", None)
|
|
21
|
+
if docker:
|
|
22
|
+
for stack in docker.stacks.values():
|
|
23
|
+
stack_ws: WebService | None = getattr(stack, "web_services", None)
|
|
24
|
+
if stack_ws:
|
|
25
|
+
for ws in stack_ws.root:
|
|
26
|
+
if ws.port in all_ports:
|
|
27
|
+
errors.append(f"Duplicate port found: {ws.port}")
|
|
28
|
+
else:
|
|
29
|
+
all_ports.add(ws.port)
|
|
30
|
+
|
|
31
|
+
if errors:
|
|
32
|
+
raise ValueError("\n".join(errors))
|
|
33
|
+
|
|
34
|
+
return obj
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from pydantic import BaseModel, model_validator, field_validator, FilePath
|
|
3
3
|
from typing import Optional, Dict, Any, Literal
|
|
4
4
|
import os
|
|
5
|
+
import typer
|
|
5
6
|
|
|
6
7
|
class Creds(BaseModel):
|
|
7
8
|
username: str
|
|
@@ -23,4 +24,6 @@ class Creds(BaseModel):
|
|
|
23
24
|
raise ValueError('Mutual exclusion error: Cannot set both passwd and ssh_key_path.')
|
|
24
25
|
if not has_passwd and not has_key:
|
|
25
26
|
raise ValueError('Missing credentials: Must set either passwd or ssh_key_path.')
|
|
27
|
+
if has_passwd and not has_key:
|
|
28
|
+
typer.secho('WARNING: Using only password authentication. Some features only work with an SSH key.', fg=typer.colors.YELLOW)
|
|
26
29
|
return self
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pydantic import BaseModel, model_validator, DirectoryPath
|
|
2
|
+
from typing import Optional, Dict
|
|
3
|
+
|
|
4
|
+
from .web_services import WebServices
|
|
5
|
+
|
|
6
|
+
class Docker(BaseModel):
|
|
7
|
+
root_path: str
|
|
8
|
+
stacks: Dict[str, StackEntry]
|
|
9
|
+
|
|
10
|
+
class StackEntry(BaseModel):
|
|
11
|
+
config_path: DirectoryPath
|
|
12
|
+
web_services: Optional[WebServices] = None
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from pydantic import BaseModel, model_validator, DirectoryPath
|
|
2
|
+
from typing import Optional, Dict, Any, Literal
|
|
3
|
+
from ipaddress import IPv4Address
|
|
4
|
+
|
|
5
|
+
from .creds import Creds
|
|
6
|
+
from .web_services import WebServices
|
|
7
|
+
from .docker import Docker
|
|
8
|
+
from .lxc import LXCs
|
|
9
|
+
from .vm import VMs
|
|
10
|
+
from .custom_types import HostType, OSType
|
|
11
|
+
from .common_validators.web_services import check_duplicate_ws_ports
|
|
12
|
+
|
|
13
|
+
class Host(BaseModel):
|
|
14
|
+
name: str = ""
|
|
15
|
+
type: HostType = "bare-metal"
|
|
16
|
+
os: OSType
|
|
17
|
+
ip: IPv4Address
|
|
18
|
+
creds: Optional[Creds] = None
|
|
19
|
+
lxc: Optional[LXCs] = None
|
|
20
|
+
vm: Optional[VMs] = None
|
|
21
|
+
docker: Optional[Docker] = None
|
|
22
|
+
web_services: Optional[WebServices] = None
|
|
23
|
+
|
|
24
|
+
@model_validator(mode="after")
|
|
25
|
+
def check_proxmox_support(self) -> "Host":
|
|
26
|
+
if self.type != "proxmox":
|
|
27
|
+
if self.lxc is not None or self.vm is not None:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"Fields 'lxc' and 'vm' are only allowed when type is 'proxmox'"
|
|
30
|
+
)
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
@model_validator(mode="after")
|
|
34
|
+
def check_duplicate_vmid(self) -> "Host":
|
|
35
|
+
all_ids: set[int] = set()
|
|
36
|
+
errors: list[str] = []
|
|
37
|
+
if self.lxc:
|
|
38
|
+
for lxc_obj in self.lxc.values():
|
|
39
|
+
if lxc_obj.vmid in all_ids:
|
|
40
|
+
errors.append(f"Duplicate vmid found: {lxc_obj.vmid}")
|
|
41
|
+
else:
|
|
42
|
+
all_ids.add(lxc_obj.vmid)
|
|
43
|
+
if self.vm:
|
|
44
|
+
for vm_obj in self.vm.values():
|
|
45
|
+
if vm_obj.vmid in all_ids:
|
|
46
|
+
errors.append(f"Duplicate vmid found: {vm_obj.vmid}")
|
|
47
|
+
else:
|
|
48
|
+
all_ids.add(vm_obj.vmid)
|
|
49
|
+
if errors:
|
|
50
|
+
raise ValueError("\n".join(errors))
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
@model_validator(mode="after")
|
|
54
|
+
def check_duplicate_ws_ports(self) -> "Host":
|
|
55
|
+
return check_duplicate_ws_ports(self)
|
|
56
|
+
|
|
57
|
+
@model_validator(mode="after")
|
|
58
|
+
def propagate_lxc_vm_names(self) -> "Host":
|
|
59
|
+
# Inject the dictionary key as the 'name' attribute for child LXCs
|
|
60
|
+
if self.lxc:
|
|
61
|
+
for k, v in self.lxc.items():
|
|
62
|
+
v.name: str = k
|
|
63
|
+
|
|
64
|
+
# Inject the dictionary key as the 'name' attribute for child VMs
|
|
65
|
+
if self.vm:
|
|
66
|
+
for k, v in self.vm.items():
|
|
67
|
+
v.name: str = k
|
|
68
|
+
|
|
69
|
+
return self
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pydantic import BaseModel, DirectoryPath, model_validator
|
|
2
|
+
from ipaddress import IPv4Address
|
|
3
|
+
from typing import Optional, Dict
|
|
4
|
+
|
|
5
|
+
from .creds import Creds
|
|
6
|
+
from .web_services import WebServices
|
|
7
|
+
from .docker import Docker
|
|
8
|
+
|
|
9
|
+
from .custom_types import OSType
|
|
10
|
+
from .common_validators.web_services import check_duplicate_ws_ports
|
|
11
|
+
|
|
12
|
+
class LXC(BaseModel):
|
|
13
|
+
name: str = ""
|
|
14
|
+
ip: IPv4Address
|
|
15
|
+
os: OSType
|
|
16
|
+
vmid: int
|
|
17
|
+
creds: Optional[Creds] = None
|
|
18
|
+
web_services: Optional[WebServices] = None
|
|
19
|
+
docker: Optional[Docker] = None
|
|
20
|
+
|
|
21
|
+
@model_validator(mode="after")
|
|
22
|
+
def validate_ws_ports(self) -> "LXC":
|
|
23
|
+
return check_duplicate_ws_ports(self)
|
|
24
|
+
|
|
25
|
+
LXCs = Dict[str, LXC]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
from pydantic import BaseModel, model_validator, DirectoryPath
|
|
3
|
+
from typing import Optional, Dict, Any, Literal
|
|
4
|
+
from ipaddress import IPv4Address
|
|
5
|
+
|
|
6
|
+
from .creds import Creds
|
|
7
|
+
from .web_services import WebServices
|
|
8
|
+
from .docker import Docker
|
|
9
|
+
from .lxc import LXC
|
|
10
|
+
from .custom_types import OSType, HostType
|
|
11
|
+
from .common_validators.web_services import check_duplicate_ws_ports
|
|
12
|
+
|
|
13
|
+
class VM(BaseModel):
|
|
14
|
+
name: str = ""
|
|
15
|
+
type: HostType = "bare-metal"
|
|
16
|
+
os: OSType
|
|
17
|
+
ip: IPv4Address
|
|
18
|
+
vmid: int
|
|
19
|
+
creds: Optional[Creds] = None
|
|
20
|
+
lxc: Optional[Dict[str, LXC]] = None
|
|
21
|
+
vm: Optional[Dict[str, "VM"]] = None ## CHECK THIS
|
|
22
|
+
web_services: Optional[WebServices] = None
|
|
23
|
+
docker: Optional[Docker] = None
|
|
24
|
+
|
|
25
|
+
@model_validator(mode="after")
|
|
26
|
+
def validate_ws_ports(self) -> "VM":
|
|
27
|
+
return check_duplicate_ws_ports(self)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
VMs = Dict[str, VM]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pydantic import BaseModel, model_validator, DirectoryPath, RootModel
|
|
2
|
+
from typing import Optional, Dict, List, Generator
|
|
3
|
+
|
|
4
|
+
class WebServices(RootModel):
|
|
5
|
+
root: List[WebService]
|
|
6
|
+
|
|
7
|
+
def __getitem__(self, item: int) -> WebService:
|
|
8
|
+
return self.root[item]
|
|
9
|
+
|
|
10
|
+
class WebService(BaseModel):
|
|
11
|
+
port: int
|
|
12
|
+
proxy_name: Optional[str] = None
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from pydantic import BaseModel, model_validator
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
from ipaddress import IPv4Address
|
|
4
|
+
|
|
5
|
+
from .lxc import LXC
|
|
6
|
+
from .vm import VM
|
|
7
|
+
from .web_services import WebService
|
|
8
|
+
from .docker import Docker, StackEntry
|
|
9
|
+
from models.input_conf.host import Host
|
|
10
|
+
from models.input_conf.settings import Settings
|
|
11
|
+
|
|
12
|
+
class YamlRoot(BaseModel):
|
|
13
|
+
settings: Settings
|
|
14
|
+
hosts: Optional[Dict[str, Host]] = None
|
|
15
|
+
|
|
16
|
+
@model_validator(mode="after")
|
|
17
|
+
def propagate_host_names(self) -> "YamlRoot":
|
|
18
|
+
if self.hosts:
|
|
19
|
+
for k, host in self.hosts.items():
|
|
20
|
+
host.name = k
|
|
21
|
+
return self
|
|
22
|
+
|
|
23
|
+
@model_validator(mode="after")
|
|
24
|
+
def validate_unique_ips(self) -> "YamlRoot":
|
|
25
|
+
|
|
26
|
+
all_ips: set[IPv4Address] = set()
|
|
27
|
+
errors: list[str] = []
|
|
28
|
+
|
|
29
|
+
def check_ips(node: object) -> None:
|
|
30
|
+
# Check for an 'ip' attribute of type IPv4Address
|
|
31
|
+
ip : IPv4Address | None = getattr(node, "ip", None)
|
|
32
|
+
if isinstance(ip, IPv4Address):
|
|
33
|
+
if ip in all_ips:
|
|
34
|
+
errors.append(f"Duplicate IP address found across configuration: '{ip}'")
|
|
35
|
+
else:
|
|
36
|
+
all_ips.add(ip)
|
|
37
|
+
# Check for lxc and vm attributes that are dict-like
|
|
38
|
+
lxc: LXC | None = getattr(node, "lxc", None)
|
|
39
|
+
if isinstance(lxc, dict):
|
|
40
|
+
for lxc_node in lxc.values():
|
|
41
|
+
check_ips(lxc_node)
|
|
42
|
+
vm: VM | None = getattr(node, "vm", None)
|
|
43
|
+
if isinstance(vm, dict):
|
|
44
|
+
for vm_node in vm.values():
|
|
45
|
+
check_ips(vm_node)
|
|
46
|
+
|
|
47
|
+
if self.hosts:
|
|
48
|
+
for host in self.hosts.values():
|
|
49
|
+
check_ips(host)
|
|
50
|
+
|
|
51
|
+
if errors:
|
|
52
|
+
raise ValueError("\n".join(errors))
|
|
53
|
+
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
@model_validator(mode="after")
|
|
57
|
+
def validate_unique_names_and_proxy_names(self) -> "YamlRoot":
|
|
58
|
+
all_names: set[str] = set()
|
|
59
|
+
all_proxy_names: set[str] = set()
|
|
60
|
+
errors: list[str] = []
|
|
61
|
+
|
|
62
|
+
def check_proxy_names(node: object) -> None:
|
|
63
|
+
web_services: WebService | None = getattr(node, "web_services", None)
|
|
64
|
+
if web_services:
|
|
65
|
+
for ws in getattr(web_services, "root", []):
|
|
66
|
+
proxy_name: str | None = getattr(ws, "proxy_name", None)
|
|
67
|
+
if proxy_name:
|
|
68
|
+
if proxy_name in all_proxy_names:
|
|
69
|
+
errors.append(f"Duplicate proxy_name found across configuration: '{proxy_name}'")
|
|
70
|
+
else:
|
|
71
|
+
all_proxy_names.add(proxy_name)
|
|
72
|
+
docker: Docker | None = getattr(node, "docker", None)
|
|
73
|
+
if docker:
|
|
74
|
+
stacks: dict[str, StackEntry] = getattr(docker, "stacks", {})
|
|
75
|
+
for stack in stacks.values():
|
|
76
|
+
stack_ws: WebService | None = getattr(stack, "web_services", None)
|
|
77
|
+
if stack_ws:
|
|
78
|
+
for ws in getattr(stack_ws, "root", []):
|
|
79
|
+
proxy_name = getattr(ws, "proxy_name", None)
|
|
80
|
+
if proxy_name:
|
|
81
|
+
if proxy_name in all_proxy_names:
|
|
82
|
+
errors.append(f"Duplicate proxy_name found across configuration: '{proxy_name}'")
|
|
83
|
+
else:
|
|
84
|
+
all_proxy_names.add(proxy_name)
|
|
85
|
+
lxc: LXC | None = getattr(node, "lxc", None)
|
|
86
|
+
if isinstance(lxc, dict):
|
|
87
|
+
for lxc_node in lxc.values():
|
|
88
|
+
check_proxy_names(lxc_node)
|
|
89
|
+
vm: VM | None = getattr(node, "vm", None)
|
|
90
|
+
if isinstance(vm, dict):
|
|
91
|
+
for vm_node in vm.values():
|
|
92
|
+
check_proxy_names(vm_node)
|
|
93
|
+
|
|
94
|
+
if self.hosts:
|
|
95
|
+
for k, host in self.hosts.items():
|
|
96
|
+
if k in all_names:
|
|
97
|
+
errors.append(f"Duplicate name found across configuration: '{k}'")
|
|
98
|
+
else:
|
|
99
|
+
all_names.add(k)
|
|
100
|
+
|
|
101
|
+
if host.lxc:
|
|
102
|
+
for lxc_name in host.lxc.keys():
|
|
103
|
+
if lxc_name in all_names:
|
|
104
|
+
errors.append(f"Duplicate name found across configuration: '{lxc_name}'")
|
|
105
|
+
else:
|
|
106
|
+
all_names.add(lxc_name)
|
|
107
|
+
|
|
108
|
+
if host.vm:
|
|
109
|
+
for vm_name in host.vm.keys():
|
|
110
|
+
if vm_name in all_names:
|
|
111
|
+
errors.append(f"Duplicate name found across configuration: '{vm_name}'")
|
|
112
|
+
else:
|
|
113
|
+
all_names.add(vm_name)
|
|
114
|
+
|
|
115
|
+
check_proxy_names(host)
|
|
116
|
+
|
|
117
|
+
if errors:
|
|
118
|
+
raise ValueError("\n".join(errors))
|
|
119
|
+
|
|
120
|
+
return self
|
|
@@ -4,7 +4,7 @@ from rich.table import Table
|
|
|
4
4
|
|
|
5
5
|
from src.cli.core import get_model, resolve_targets, console, state
|
|
6
6
|
from models.input_conf.yaml_root import YamlRoot
|
|
7
|
-
from models.input_conf.
|
|
7
|
+
from models.input_conf.host import Host
|
|
8
8
|
import src.host as host
|
|
9
9
|
|
|
10
10
|
app = typer.Typer(help="Manage bare-metal hosts.", no_args_is_help=True)
|
|
@@ -5,7 +5,8 @@ from rich.table import Table
|
|
|
5
5
|
|
|
6
6
|
from src.cli.core import get_model, resolve_targets, console, state
|
|
7
7
|
from models.input_conf.yaml_root import YamlRoot
|
|
8
|
-
from models.input_conf.
|
|
8
|
+
from models.input_conf.host import Host
|
|
9
|
+
from models.input_conf.lxc import LXC
|
|
9
10
|
import src.lxc as lxc
|
|
10
11
|
|
|
11
12
|
app = typer.Typer(help="Manage Proxmox LXC containers from Config.", no_args_is_help=True)
|
|
@@ -5,11 +5,11 @@ from rich.table import Table
|
|
|
5
5
|
|
|
6
6
|
from src.cli.core import ConfigError, get_model, console
|
|
7
7
|
from models.input_conf.yaml_root import YamlRoot
|
|
8
|
-
from models.input_conf.
|
|
8
|
+
from models.input_conf.host import Host
|
|
9
9
|
|
|
10
10
|
app = typer.Typer(help="Validate configuration.")
|
|
11
11
|
|
|
12
|
-
@app.callback()
|
|
12
|
+
@app.callback(invoke_without_command=True)
|
|
13
13
|
def validate(ctx: typer.Context) -> None:
|
|
14
14
|
if ctx.invoked_subcommand:
|
|
15
15
|
return
|
|
@@ -4,7 +4,7 @@ from rich.table import Table
|
|
|
4
4
|
|
|
5
5
|
from src.cli.core import get_model, resolve_targets, console
|
|
6
6
|
from models.input_conf.yaml_root import YamlRoot
|
|
7
|
-
from models.input_conf.
|
|
7
|
+
from models.input_conf.host import Host
|
|
8
8
|
import src.vm as vm
|
|
9
9
|
|
|
10
10
|
app = typer.Typer(help="Manage virtual machines.", no_args_is_help=True)
|
|
@@ -2,7 +2,7 @@ from ansible_runner.runner import Runner
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
import getpass
|
|
4
4
|
|
|
5
|
-
from models.input_conf.
|
|
5
|
+
from models.input_conf.host import Host
|
|
6
6
|
from models.input_conf.creds import Creds
|
|
7
7
|
from src.utils.ansible_runner import run_playbook
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from ansible_runner.runner import Runner
|
|
2
|
-
from models.input_conf.
|
|
3
|
-
from models.input_conf.
|
|
2
|
+
from models.input_conf.host import Host
|
|
3
|
+
from models.input_conf.host import OSType
|
|
4
4
|
from models.input_conf.creds import Creds
|
|
5
5
|
from src.utils.ansible_runner import run_playbook
|
|
6
6
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from src.utils.ansible_runner import run_playbook
|
|
2
2
|
from models.input_conf.yaml_root import YamlRoot
|
|
3
|
-
from models.input_conf.
|
|
3
|
+
from models.input_conf.host import Host
|
|
4
|
+
from models.input_conf.lxc import LXC
|
|
4
5
|
|
|
5
6
|
def findAll(config: YamlRoot) -> list[tuple[Host, LXC]]:
|
|
6
7
|
"""Returns a list of all LXCs found inside Proxmox hosts in the Yaml config."""
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from src.utils.ansible_runner import run_playbook
|
|
2
|
-
from models.input_conf.
|
|
2
|
+
from models.input_conf.host import Host
|
|
3
|
+
from models.input_conf.lxc import LXC
|
|
3
4
|
from models.input_conf.creds import Creds
|
|
4
5
|
|
|
5
6
|
def update(proxmox_lxc_pairs: list[tuple[Host, LXC]], default_creds: Creds, dry_run: bool = False, verbose: bool = False) -> None:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict, Any, Optional
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
|
|
8
|
+
from models.input_conf.yaml_root import YamlRoot
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
def validate_yaml(raw: Dict[str, Any], rootPath: str) -> Optional[YamlRoot]:
|
|
13
|
+
base_dir: Path = Path(rootPath).parent.resolve()
|
|
14
|
+
original_dir: str = os.getcwd()
|
|
15
|
+
|
|
16
|
+
os.chdir(base_dir)
|
|
17
|
+
|
|
18
|
+
model = None
|
|
19
|
+
try:
|
|
20
|
+
# model_validate is the standard Pydantic V2 way to load from dictionaries
|
|
21
|
+
model: YamlRoot = YamlRoot.model_validate(raw)
|
|
22
|
+
|
|
23
|
+
except ValidationError as e:
|
|
24
|
+
error_messages = []
|
|
25
|
+
for error in e.errors():
|
|
26
|
+
# Extract the exact path of the failure (e.g., hosts.cprox)
|
|
27
|
+
location: str = ".".join(str(loc) for loc in error.get('loc', []))
|
|
28
|
+
|
|
29
|
+
# Clean up the redundant Pydantic prefix
|
|
30
|
+
raw_msg: str = error.get('msg', '').replace('Value error, ', '')
|
|
31
|
+
|
|
32
|
+
# A single validator may raise multiple newline-separated errors
|
|
33
|
+
for msg in raw_msg.split("\n"):
|
|
34
|
+
if location:
|
|
35
|
+
error_messages.append(f"[bold yellow]{location}[/bold yellow]: [red]{msg}[/red]")
|
|
36
|
+
else:
|
|
37
|
+
error_messages.append(f"[red]{msg}[/red]")
|
|
38
|
+
|
|
39
|
+
formatted_errors = "\n".join(f"• {msg}" for msg in error_messages)
|
|
40
|
+
|
|
41
|
+
# Print the styled error panel
|
|
42
|
+
console.print(
|
|
43
|
+
Panel(
|
|
44
|
+
formatted_errors,
|
|
45
|
+
title="[bold red]YAML Validation Failed",
|
|
46
|
+
border_style="red",
|
|
47
|
+
expand=False
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
finally:
|
|
52
|
+
# Always ensure we return to the original working directory
|
|
53
|
+
os.chdir(original_dir)
|
|
54
|
+
|
|
55
|
+
return model
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
from models.input_conf.yaml_root import YamlRoot
|
|
2
|
-
from models.input_conf.
|
|
2
|
+
from models.input_conf.vm import VM
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
def findAll(config: YamlRoot) -> list[
|
|
5
|
+
def findAll(config: YamlRoot) -> list[VM]:
|
|
6
6
|
if config.hosts is None:
|
|
7
7
|
raise KeyError("No hosts are defined in the configuration.")
|
|
8
8
|
|
|
9
|
-
vms: list[
|
|
9
|
+
vms: list[VM] = []
|
|
10
10
|
for host in config.hosts.values():
|
|
11
11
|
if host.vm is not None:
|
|
12
12
|
vms.extend(host.vm.values())
|
|
13
13
|
return vms
|
|
14
14
|
|
|
15
|
-
def find(config: YamlRoot, targets: list[str]) -> list[
|
|
15
|
+
def find(config: YamlRoot, targets: list[str]) -> list[VM]:
|
|
16
16
|
if config.hosts is None:
|
|
17
17
|
raise KeyError("No hosts are defined in the configuration.")
|
|
18
18
|
|
|
19
|
-
all_vms: dict[str,
|
|
19
|
+
all_vms: dict[str, VM] = {}
|
|
20
20
|
for host in config.hosts.values():
|
|
21
21
|
if host.vm is not None:
|
|
22
22
|
all_vms.update(host.vm)
|
|
23
23
|
|
|
24
|
-
found_vms: list[
|
|
24
|
+
found_vms: list[VM] = []
|
|
25
25
|
for target in targets:
|
|
26
26
|
found = False
|
|
27
27
|
if target in all_vms:
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
settings:
|
|
2
|
+
default_creds:
|
|
3
|
+
username: root
|
|
4
|
+
passwd: changeme
|
|
5
|
+
#ssh_key_path: xxxxx
|
|
6
|
+
dns:
|
|
7
|
+
local_dns_suffix: .lab
|
|
8
|
+
pihole_location: 10.0.10.5
|
|
9
|
+
proxy:
|
|
10
|
+
proxy_suffix: .mfritz.top
|
|
11
|
+
proxy_location: 10.0.10.6
|
|
12
|
+
|
|
13
|
+
hosts:
|
|
14
|
+
proxmox_test:
|
|
15
|
+
type: proxmox
|
|
16
|
+
os: debian
|
|
17
|
+
ip: 10.0.10.4
|
|
18
|
+
lxc:
|
|
19
|
+
wireguard:
|
|
20
|
+
ip: 10.0.10.8
|
|
21
|
+
os: alpine
|
|
22
|
+
vmid: 101
|
|
23
|
+
|
|
24
|
+
cprox:
|
|
25
|
+
type: proxmox
|
|
26
|
+
os: debian
|
|
27
|
+
ip: 10.0.10.3
|
|
28
|
+
lxc:
|
|
29
|
+
wireguard:
|
|
30
|
+
ip: 10.0.10.2
|
|
31
|
+
os: alpine
|
|
32
|
+
vmid: 100
|
|
33
|
+
tailscale:
|
|
34
|
+
ip: 10.0.20.3
|
|
35
|
+
os: alpine
|
|
36
|
+
vmid: 101
|
|
37
|
+
docker:
|
|
38
|
+
ip: 10.0.10.6
|
|
39
|
+
os: debian
|
|
40
|
+
vmid: 102
|
|
41
|
+
docker:
|
|
42
|
+
root_path: "/home/manuel"
|
|
43
|
+
stacks:
|
|
44
|
+
nginx-proxy-manager:
|
|
45
|
+
config_path: "./docker/nginx/"
|
|
46
|
+
web_services:
|
|
47
|
+
- proxy_name: home
|
|
48
|
+
port: 81
|
|
49
|
+
nebula-sync:
|
|
50
|
+
config_path: "./docker/nebula-sync/"
|
|
51
|
+
dfs-aip-interface:
|
|
52
|
+
config_path: "./docker/homeassistant/"
|
|
53
|
+
web_services:
|
|
54
|
+
- proxy_name: dfs-aip
|
|
55
|
+
port: 8081
|
|
56
|
+
|
|
57
|
+
home:
|
|
58
|
+
ip: 10.0.10.10
|
|
59
|
+
os: debian
|
|
60
|
+
vmid: 103
|
|
61
|
+
docker:
|
|
62
|
+
root_path: "/home/manuel"
|
|
63
|
+
stacks:
|
|
64
|
+
homeassistant:
|
|
65
|
+
config_path: "./docker/homeassistant/"
|
|
66
|
+
web_services:
|
|
67
|
+
- proxy_name: esphome
|
|
68
|
+
port: 6052
|
|
69
|
+
- proxy_name: home
|
|
70
|
+
port: 8123
|
|
71
|
+
- proxy_name: z2mqtt
|
|
72
|
+
port: 8080
|
|
73
|
+
|
|
74
|
+
pihole:
|
|
75
|
+
ip: 10.0.10.5
|
|
76
|
+
os: debian
|
|
77
|
+
vmid: 105
|
|
78
|
+
web_services:
|
|
79
|
+
- proxy_name: pihole
|
|
80
|
+
port: 8080
|
|
81
|
+
|
|
82
|
+
vm:
|
|
83
|
+
fr24-radar:
|
|
84
|
+
os: debian
|
|
85
|
+
ip: 10.0.50.149
|
|
86
|
+
vmid: 111
|
|
87
|
+
|
|
88
|
+
test-system:
|
|
89
|
+
os: debian
|
|
90
|
+
ip: 10.0.10.42
|
|
91
|
+
|
|
92
|
+
tmp-pi:
|
|
93
|
+
os: debian
|
|
94
|
+
ip: 10.0.10.254
|
|
95
|
+
|
|
96
|
+
lifeboat:
|
|
97
|
+
type: bare-metal
|
|
98
|
+
os: debian
|
|
99
|
+
ip: 10.0.10.9
|
|
100
|
+
web_services:
|
|
101
|
+
- proxy_name: pihole2
|
|
102
|
+
port: 8080
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel, model_validator, DirectoryPath
|
|
2
|
-
from typing import Optional, Dict, Any, Literal
|
|
3
|
-
from ipaddress import IPv4Address
|
|
4
|
-
|
|
5
|
-
from models.input_conf.creds import Creds
|
|
6
|
-
|
|
7
|
-
OSType = Literal["debian", "alpine", "redhat"]
|
|
8
|
-
HostType = Literal["bare-metal", "proxmox"]
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class WebService(BaseModel):
|
|
12
|
-
port: int
|
|
13
|
-
proxy_name: Optional[str] = None
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class DockerStack(BaseModel):
|
|
17
|
-
config_path: DirectoryPath
|
|
18
|
-
web_services: Optional[Dict[str, WebService]] = None
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class LXC(BaseModel):
|
|
22
|
-
name: str = ""
|
|
23
|
-
ip: IPv4Address
|
|
24
|
-
os: OSType
|
|
25
|
-
vmid: int
|
|
26
|
-
creds: Optional[Creds] = None
|
|
27
|
-
web_services: Optional[Dict[str, WebService]] = None
|
|
28
|
-
docker_stack: Optional[Dict[str, DockerStack]] = None
|
|
29
|
-
|
|
30
|
-
class Host(BaseModel):
|
|
31
|
-
name: str = ""
|
|
32
|
-
type: HostType = "bare-metal"
|
|
33
|
-
os: OSType
|
|
34
|
-
ip: IPv4Address
|
|
35
|
-
creds: Optional[Creds] = None
|
|
36
|
-
lxc: Optional[Dict[str, LXC]] = None
|
|
37
|
-
vm: Optional[Dict[str, "Host"]] = None ## CHECK THIS
|
|
38
|
-
web_services: Optional[Dict[str, WebService]] = None
|
|
39
|
-
|
|
40
|
-
@model_validator(mode="after")
|
|
41
|
-
def check_proxmox_support(self) -> "Host":
|
|
42
|
-
if self.type != "proxmox":
|
|
43
|
-
if self.lxc is not None or self.vm is not None:
|
|
44
|
-
raise ValueError(
|
|
45
|
-
"Fields 'lxc' and 'vm' are only allowed when type is 'proxmox'"
|
|
46
|
-
)
|
|
47
|
-
return self
|
|
48
|
-
|
|
49
|
-
@model_validator(mode="after")
|
|
50
|
-
def propagate_lxc_vm_names(self) -> "Host":
|
|
51
|
-
# Inject the dictionary key as the 'name' attribute for child LXCs
|
|
52
|
-
if self.lxc:
|
|
53
|
-
for k, v in self.lxc.items():
|
|
54
|
-
v.name = k
|
|
55
|
-
|
|
56
|
-
# Inject the dictionary key as the 'name' attribute for child VMs
|
|
57
|
-
if self.vm:
|
|
58
|
-
for k, v in self.vm.items():
|
|
59
|
-
v.name = k
|
|
60
|
-
|
|
61
|
-
return self
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel, model_validator
|
|
2
|
-
from typing import Optional, Dict
|
|
3
|
-
|
|
4
|
-
from models.input_conf.hosts import Host
|
|
5
|
-
from models.input_conf.settings import Settings
|
|
6
|
-
|
|
7
|
-
class YamlRoot(BaseModel):
|
|
8
|
-
settings: Settings
|
|
9
|
-
hosts: Optional[Dict[str, Host]] = None
|
|
10
|
-
|
|
11
|
-
@model_validator(mode="after")
|
|
12
|
-
def propagate_host_names(self) -> "YamlRoot":
|
|
13
|
-
if self.hosts:
|
|
14
|
-
for k, host in self.hosts.items():
|
|
15
|
-
host.name = k
|
|
16
|
-
return self
|
|
17
|
-
|
|
18
|
-
@model_validator(mode="after")
|
|
19
|
-
def validate_unique_names(self) -> "YamlRoot":
|
|
20
|
-
all_names = set()
|
|
21
|
-
if self.hosts:
|
|
22
|
-
for k, host in self.hosts.items():
|
|
23
|
-
if k in all_names:
|
|
24
|
-
raise ValueError(f"Duplicate name found across configuration: '{k}'")
|
|
25
|
-
all_names.add(k)
|
|
26
|
-
|
|
27
|
-
if host.lxc:
|
|
28
|
-
for lxc_name in host.lxc.keys():
|
|
29
|
-
if lxc_name in all_names:
|
|
30
|
-
raise ValueError(f"Duplicate name found across configuration: '{lxc_name}'")
|
|
31
|
-
all_names.add(lxc_name)
|
|
32
|
-
|
|
33
|
-
if host.vm:
|
|
34
|
-
for vm_name in host.vm.keys():
|
|
35
|
-
if vm_name in all_names:
|
|
36
|
-
raise ValueError(f"Duplicate name found across configuration: '{vm_name}'")
|
|
37
|
-
all_names.add(vm_name)
|
|
38
|
-
return self
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Any, Optional
|
|
2
|
-
from pydantic import ValidationError
|
|
3
|
-
import os
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from models.input_conf.yaml_root import YamlRoot
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def validate_yaml(raw: Dict[str, Any], rootPath: str) -> Optional[YamlRoot]:
|
|
10
|
-
base_dir: Path = Path(rootPath).parent.resolve()
|
|
11
|
-
original_dir: str = os.getcwd()
|
|
12
|
-
os.chdir(base_dir)
|
|
13
|
-
|
|
14
|
-
model = None
|
|
15
|
-
try:
|
|
16
|
-
try:
|
|
17
|
-
model = YamlRoot(**raw)
|
|
18
|
-
except ValidationError as e:
|
|
19
|
-
print("Validation error in YAML structure:")
|
|
20
|
-
print(e)
|
|
21
|
-
finally:
|
|
22
|
-
os.chdir(original_dir)
|
|
23
|
-
|
|
24
|
-
return model
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
settings:
|
|
2
|
-
default_creds:
|
|
3
|
-
username: root
|
|
4
|
-
passwd: changeme
|
|
5
|
-
#ssh_key_path: xxxxx
|
|
6
|
-
dns:
|
|
7
|
-
local_dns_suffix: .lab
|
|
8
|
-
pihole_location: 10.0.10.5
|
|
9
|
-
proxy:
|
|
10
|
-
proxy_suffix: .mfritz.top
|
|
11
|
-
proxy_location: 10.0.10.6
|
|
12
|
-
|
|
13
|
-
hosts:
|
|
14
|
-
cprox:
|
|
15
|
-
type: proxmox
|
|
16
|
-
os: debian
|
|
17
|
-
ip: 10.0.10.3
|
|
18
|
-
lxc:
|
|
19
|
-
wireguard:
|
|
20
|
-
ip: 10.0.10.2
|
|
21
|
-
os: alpine
|
|
22
|
-
vmid: 100
|
|
23
|
-
tailscale:
|
|
24
|
-
ip: 10.0.20.3
|
|
25
|
-
os: alpine
|
|
26
|
-
vmid: 101
|
|
27
|
-
docker:
|
|
28
|
-
ip: 10.0.10.6
|
|
29
|
-
os: debian
|
|
30
|
-
vmid: 102
|
|
31
|
-
docker_stack:
|
|
32
|
-
nginx-proxy-manager:
|
|
33
|
-
config_path: "./docker/nginx/"
|
|
34
|
-
web_services:
|
|
35
|
-
app:
|
|
36
|
-
port: 81
|
|
37
|
-
proxy_name: nginx
|
|
38
|
-
nebula-sync:
|
|
39
|
-
config_path: "./docker/nebula-sync/"
|
|
40
|
-
dfs-aip-interface:
|
|
41
|
-
config_path: "./docker/homeassistant/"
|
|
42
|
-
web_services:
|
|
43
|
-
dfs-aip:
|
|
44
|
-
port: 8081
|
|
45
|
-
proxy_name: dfs-aip
|
|
46
|
-
|
|
47
|
-
home:
|
|
48
|
-
ip: 10.0.10.10
|
|
49
|
-
os: debian
|
|
50
|
-
vmid: 103
|
|
51
|
-
docker_stack:
|
|
52
|
-
homeassistant:
|
|
53
|
-
config_path: "./docker/homeassistant/"
|
|
54
|
-
web_services:
|
|
55
|
-
esphome:
|
|
56
|
-
port: 6052
|
|
57
|
-
homeassistant:
|
|
58
|
-
port: 8123
|
|
59
|
-
proxy_name: home
|
|
60
|
-
zigbee2mqtt:
|
|
61
|
-
port: 8080
|
|
62
|
-
proxy_name: z2mqtt
|
|
63
|
-
|
|
64
|
-
pihole:
|
|
65
|
-
ip: 10.0.10.5
|
|
66
|
-
os: debian
|
|
67
|
-
vmid: 105
|
|
68
|
-
web_services:
|
|
69
|
-
admin-panel:
|
|
70
|
-
port: 8080
|
|
71
|
-
proxy_name: pihole
|
|
72
|
-
|
|
73
|
-
vm:
|
|
74
|
-
fr24-radar:
|
|
75
|
-
os: debian
|
|
76
|
-
ip: 10.0.50.149
|
|
77
|
-
# for later -> Add option to add dncp
|
|
78
|
-
# dhcp-reserve: # for later
|
|
79
|
-
# ip: 10.0.30.5
|
|
80
|
-
# mac: FF:FF:FF:FF:FF:FF
|
|
81
|
-
|
|
82
|
-
test-system:
|
|
83
|
-
os: debian
|
|
84
|
-
ip: 10.0.10.42
|
|
85
|
-
|
|
86
|
-
tmp-pi:
|
|
87
|
-
os: debian
|
|
88
|
-
ip: 10.0.10.254
|
|
89
|
-
# for later -> Add option to add dncp
|
|
90
|
-
# type: dhcp
|
|
91
|
-
lifeboat:
|
|
92
|
-
type: bare-metal
|
|
93
|
-
os: debian
|
|
94
|
-
ip: 10.0.10.9
|
|
95
|
-
web_services:
|
|
96
|
-
pihole:
|
|
97
|
-
port: 8080
|
|
98
|
-
proxy_name: pihole2
|
|
99
|
-
|
|
100
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|