arcane-mage 2.2.0__tar.gz → 2.2.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.
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/PKG-INFO +1 -1
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/pyproject.toml +1 -1
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/config.py +4 -1
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/network.py +21 -13
- arcane_mage-2.2.1/tests/test_models/test_chain_sources.py +79 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/uv.lock +1 -1
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/.claude/settings.local.json +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/.gitignore +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/CHANGELOG.md +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/LICENSE +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/README.md +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/README.md +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/galaxy.yml +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/meta/runtime.yml +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/plugins/modules/deprovision.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/plugins/modules/iso_info.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/plugins/modules/provision.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/plugins/modules/status.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/plugins/modules/validate.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/docs/configuration.md +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/docs/keyring.md +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/docs/proxmox-setup.md +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/examples/all_settings.yaml +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/examples/merge_anchors.yaml +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/examples/minimal.yaml +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/examples/typical.yaml +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/proxmox_local_storage.png +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/proxmox_permissions.png +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/proxmox_user.png +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/proxmox_user_api_token.png +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/__init__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/__main__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/arcane_mage.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/arcane_mage.tcss +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/fat_writer.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/helpers.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/images/__init__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/images/arcane_config.raw.gz +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/images/arcane_efi.raw.gz +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/log.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/messages.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/__init__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/delegate.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/fluxnode.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/hypervisor.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/identity.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/installer.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/notifications.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/models/system.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/password.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/provisioner.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/proxmox.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/py.typed +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/screens/__init__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/screens/confirm_screen.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/screens/hypervisor.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/screens/provision_node.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/screens/provisioning_info.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/screens/welcome.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/screens/welcome_proxmox.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/tpm_keyring.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/widgets/__init__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/src/arcane_mage/widgets/spinner.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/testing123.yaml +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/__init__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/conftest.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_fat_writer.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_helpers.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/__init__.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_config_group.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_delegate.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_fluxnode.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_hypervisor.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_identity.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_installer.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_network.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_notifications.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_models/test_system.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_password.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_provisioner.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_proxmox.py +0 -0
- {arcane_mage-2.2.0 → arcane_mage-2.2.1}/tests/test_public_api.py +0 -0
|
@@ -59,7 +59,10 @@ class ArcaneOsConfig:
|
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
async def write_installer_config(self, file_path: Path) -> bool:
|
|
62
|
-
|
|
62
|
+
chain = self.fluxnode.chain
|
|
63
|
+
res = await self.fluxnode.network.write_installer_config(
|
|
64
|
+
file_path, labnet=chain is not None and chain.network == "labnet"
|
|
65
|
+
)
|
|
63
66
|
|
|
64
67
|
return res
|
|
65
68
|
|
|
@@ -67,12 +67,7 @@ class Link:
|
|
|
67
67
|
@property
|
|
68
68
|
def ethernet(self) -> bool:
|
|
69
69
|
# not sure how this applies for wlan interfaces as I don't have one to test
|
|
70
|
-
return (
|
|
71
|
-
self.kind is None
|
|
72
|
-
and self.name != "lo"
|
|
73
|
-
and bool(self.address)
|
|
74
|
-
and self.address != "00:00:00:00:00:00"
|
|
75
|
-
)
|
|
70
|
+
return self.kind is None and self.name != "lo" and bool(self.address) and self.address != "00:00:00:00:00:00"
|
|
76
71
|
|
|
77
72
|
|
|
78
73
|
@py_dataclass
|
|
@@ -137,11 +132,7 @@ class Route:
|
|
|
137
132
|
if not isinstance(other, Route):
|
|
138
133
|
return False
|
|
139
134
|
|
|
140
|
-
return
|
|
141
|
-
self.dst == other.dst
|
|
142
|
-
and self.link == other.link
|
|
143
|
-
and self.gateway == other.gateway
|
|
144
|
-
)
|
|
135
|
+
return self.dst == other.dst and self.link == other.link and self.gateway == other.gateway
|
|
145
136
|
|
|
146
137
|
|
|
147
138
|
@py_dataclass
|
|
@@ -264,6 +255,20 @@ class NetworkConfig:
|
|
|
264
255
|
return confs
|
|
265
256
|
|
|
266
257
|
|
|
258
|
+
def chain_source_address(ip: IPv4Address, *, labnet: bool) -> bool:
|
|
259
|
+
"""Whether a node may stream its chain from `ip`: a unicast address on a LAN,
|
|
260
|
+
or, installing onto labnet, on labnet's internet too. Loopback, link-local,
|
|
261
|
+
multicast, unspecified, reserved and broadcast addresses are never a source,
|
|
262
|
+
though Python counts several of them private."""
|
|
263
|
+
if ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_unspecified or ip.is_reserved:
|
|
264
|
+
return False
|
|
265
|
+
if ip == IPv4Address("255.255.255.255"):
|
|
266
|
+
return False
|
|
267
|
+
if ip.is_private:
|
|
268
|
+
return True
|
|
269
|
+
return labnet and ip.is_global
|
|
270
|
+
|
|
271
|
+
|
|
267
272
|
@py_dataclass
|
|
268
273
|
class FluxnodeNetworkConfig:
|
|
269
274
|
"""Fluxnode-specific network settings such as UPnP and private chain sources."""
|
|
@@ -275,7 +280,10 @@ class FluxnodeNetworkConfig:
|
|
|
275
280
|
def to_dict(self) -> dict:
|
|
276
281
|
return TypeAdapter(type(self)).dump_python(self, mode="json", exclude_defaults=True)
|
|
277
282
|
|
|
278
|
-
async def write_installer_config(self, file_path: Path | str) -> bool:
|
|
283
|
+
async def write_installer_config(self, file_path: Path | str, *, labnet: bool = False) -> bool:
|
|
284
|
+
"""Writes the installer's network section. A chain source is kept only at a
|
|
285
|
+
unicast address a node can stream from: on a LAN, or, for a node installing
|
|
286
|
+
onto labnet, also on labnet's internet, where the lab's donor answers."""
|
|
279
287
|
# we let the configure app set the local chain sources
|
|
280
288
|
ip_pattern = r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
|
|
281
289
|
port_pattern = (
|
|
@@ -294,7 +302,7 @@ class FluxnodeNetworkConfig:
|
|
|
294
302
|
if not re.match(ip_pattern, ip) or not re.match(port_pattern, port):
|
|
295
303
|
continue
|
|
296
304
|
|
|
297
|
-
if not IPv4Address(ip)
|
|
305
|
+
if not chain_source_address(IPv4Address(ip), labnet=labnet):
|
|
298
306
|
continue
|
|
299
307
|
|
|
300
308
|
filtered_chain_sources.append(chain_source)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from arcane_mage.models.config import ArcaneOsConfig
|
|
9
|
+
|
|
10
|
+
LAB_KEY = "020483178bc32818eb9922b68ac0771fe95136a6edf746016125270cfc2111fd5a"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def written_sources(tmp_path: Path, node: dict, sources: list[str], chain: dict | None) -> list[str]:
|
|
14
|
+
node["fluxnode"]["network"] = {"private_chain_sources": sources}
|
|
15
|
+
if chain is not None:
|
|
16
|
+
node["fluxnode"]["chain"] = chain
|
|
17
|
+
path = tmp_path / "installer.yaml"
|
|
18
|
+
assert await ArcaneOsConfig.from_dict(node).write_installer_config(path)
|
|
19
|
+
return yaml.safe_load(path.read_text())["network"]["private_chain_sources"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def test_a_mainnet_install_keeps_only_private_chain_sources(tmp_path: Path, minimal_node_dict: dict):
|
|
23
|
+
sources = ["192.168.1.20:16127", "10.0.0.5:16137", "25.47.0.2:16127", "8.8.8.8:16127"]
|
|
24
|
+
|
|
25
|
+
kept = await written_sources(tmp_path, minimal_node_dict, sources, None)
|
|
26
|
+
|
|
27
|
+
assert kept == ["192.168.1.20:16127", "10.0.0.5:16137"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def test_a_labnet_install_keeps_the_labs_donor_on_its_internet(tmp_path: Path, minimal_node_dict: dict):
|
|
31
|
+
sources = ["25.47.0.2:16127", "192.168.147.10:16127"]
|
|
32
|
+
|
|
33
|
+
kept = await written_sources(tmp_path, minimal_node_dict, sources, {"network": "labnet", "key": LAB_KEY})
|
|
34
|
+
|
|
35
|
+
assert kept == ["25.47.0.2:16127", "192.168.147.10:16127"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize(
|
|
39
|
+
"source",
|
|
40
|
+
[
|
|
41
|
+
"127.0.0.1:16127",
|
|
42
|
+
"0.0.0.0:16127",
|
|
43
|
+
"169.254.10.1:16127",
|
|
44
|
+
"224.0.0.1:16127",
|
|
45
|
+
"255.255.255.255:16127",
|
|
46
|
+
"240.0.0.1:16127",
|
|
47
|
+
"25.47.0.2:99999",
|
|
48
|
+
"25.47.0.2",
|
|
49
|
+
"host.example:16127",
|
|
50
|
+
],
|
|
51
|
+
)
|
|
52
|
+
async def test_a_labnet_install_still_drops_what_is_no_unicast_source(
|
|
53
|
+
tmp_path: Path, minimal_node_dict: dict, source: str
|
|
54
|
+
):
|
|
55
|
+
kept = await written_sources(tmp_path, minimal_node_dict, [source], {"network": "labnet", "key": LAB_KEY})
|
|
56
|
+
|
|
57
|
+
assert kept == []
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def test_an_explicit_mainnet_chain_keeps_only_private_chain_sources(tmp_path: Path, minimal_node_dict: dict):
|
|
61
|
+
kept = await written_sources(tmp_path, minimal_node_dict, ["25.47.0.2:16127"], {"network": "mainnet"})
|
|
62
|
+
|
|
63
|
+
assert kept == []
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize(
|
|
67
|
+
"source",
|
|
68
|
+
[
|
|
69
|
+
"127.0.0.1:16127",
|
|
70
|
+
"0.0.0.0:16127",
|
|
71
|
+
"169.254.10.1:16127",
|
|
72
|
+
"255.255.255.255:16127",
|
|
73
|
+
"240.0.0.1:16127",
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
async def test_a_mainnet_install_drops_what_is_no_lan_source(tmp_path: Path, minimal_node_dict: dict, source: str):
|
|
77
|
+
kept = await written_sources(tmp_path, minimal_node_dict, [source], None)
|
|
78
|
+
|
|
79
|
+
assert kept == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{arcane_mage-2.2.0 → arcane_mage-2.2.1}/ansible_collection/runonflux/arcane_mage/meta/runtime.yml
RENAMED
|
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
|
|
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
|