easymanet 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- easymanet-0.1.0/PKG-INFO +59 -0
- easymanet-0.1.0/README.md +31 -0
- easymanet-0.1.0/apps/cli/src/easymanet_cli/__init__.py +1 -0
- easymanet-0.1.0/apps/cli/src/easymanet_cli/app.py +162 -0
- easymanet-0.1.0/apps/cli/src/easymanet_cli/common.py +57 -0
- easymanet-0.1.0/apps/cli/src/easymanet_cli/flash.py +437 -0
- easymanet-0.1.0/easymanet.egg-info/PKG-INFO +59 -0
- easymanet-0.1.0/easymanet.egg-info/SOURCES.txt +61 -0
- easymanet-0.1.0/easymanet.egg-info/dependency_links.txt +1 -0
- easymanet-0.1.0/easymanet.egg-info/entry_points.txt +2 -0
- easymanet-0.1.0/easymanet.egg-info/requires.txt +10 -0
- easymanet-0.1.0/easymanet.egg-info/top_level.txt +3 -0
- easymanet-0.1.0/images/openmanet/provisioning/extra-packages.txt +6 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/README.md +18 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/easymanet/provision.json +3 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/init.d/easymanet-boot-report +13 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/init.d/easymanet-management-lan +22 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/sysctl.d/99-easymanet.conf +18 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/uci-defaults/97-easymanet-management-lan +7 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/uci-defaults/98-easymanet-boot-report +8 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/uci-defaults/99-easymanet +24 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/boot-report.sh +125 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/network.sh +86 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/provision-lib.sh +75 -0
- easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/provision.sh +532 -0
- easymanet-0.1.0/packages/core/src/easymanet/__init__.py +3 -0
- easymanet-0.1.0/packages/core/src/easymanet/disks/__init__.py +73 -0
- easymanet-0.1.0/packages/core/src/easymanet/disks/_common.py +88 -0
- easymanet-0.1.0/packages/core/src/easymanet/disks/core.py +139 -0
- easymanet-0.1.0/packages/core/src/easymanet/disks/linux.py +306 -0
- easymanet-0.1.0/packages/core/src/easymanet/disks/macos.py +343 -0
- easymanet-0.1.0/packages/core/src/easymanet/download.py +563 -0
- easymanet-0.1.0/packages/core/src/easymanet/format.py +13 -0
- easymanet-0.1.0/packages/core/src/easymanet/image.py +323 -0
- easymanet-0.1.0/packages/core/src/easymanet/inject.py +322 -0
- easymanet-0.1.0/packages/core/src/easymanet/manifest.py +91 -0
- easymanet-0.1.0/packages/core/src/easymanet/platform.py +25 -0
- easymanet-0.1.0/packages/core/src/easymanet/privileges.py +39 -0
- easymanet-0.1.0/packages/core/src/easymanet/render.py +60 -0
- easymanet-0.1.0/packages/core/src/easymanet/validate.py +338 -0
- easymanet-0.1.0/packages/core/src/easymanet/workspace.py +132 -0
- easymanet-0.1.0/packages/image/src/easymanet_image/__init__.py +1 -0
- easymanet-0.1.0/packages/image/src/easymanet_image/build.py +410 -0
- easymanet-0.1.0/packages/image/src/easymanet_image/cli.py +240 -0
- easymanet-0.1.0/packages/image/src/easymanet_image/release.py +105 -0
- easymanet-0.1.0/pyproject.toml +94 -0
- easymanet-0.1.0/setup.cfg +4 -0
- easymanet-0.1.0/tests/test_build.py +249 -0
- easymanet-0.1.0/tests/test_cli.py +383 -0
- easymanet-0.1.0/tests/test_cli_common.py +17 -0
- easymanet-0.1.0/tests/test_disks.py +543 -0
- easymanet-0.1.0/tests/test_download.py +467 -0
- easymanet-0.1.0/tests/test_extra_packages.py +22 -0
- easymanet-0.1.0/tests/test_firstboot.py +147 -0
- easymanet-0.1.0/tests/test_image.py +508 -0
- easymanet-0.1.0/tests/test_inject.py +248 -0
- easymanet-0.1.0/tests/test_manifest.py +153 -0
- easymanet-0.1.0/tests/test_packaging.py +129 -0
- easymanet-0.1.0/tests/test_privileges.py +41 -0
- easymanet-0.1.0/tests/test_provision_behavior.py +508 -0
- easymanet-0.1.0/tests/test_render.py +217 -0
- easymanet-0.1.0/tests/test_sysctl.py +18 -0
- easymanet-0.1.0/tests/test_validate.py +418 -0
easymanet-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: easymanet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-touch OpenMANET provisioning and imaging
|
|
5
|
+
Author: EasyMANET Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: openmanet,mesh,provisioning,openwrt
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: System Administrators
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
16
|
+
Classifier: Topic :: System :: Systems Administration
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: typer>=0.9
|
|
20
|
+
Requires-Dist: pyyaml>=6
|
|
21
|
+
Requires-Dist: rich>=13
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
|
+
Requires-Dist: setuptools>=68; extra == "dev"
|
|
26
|
+
Requires-Dist: tomli>=2; extra == "dev"
|
|
27
|
+
Requires-Dist: wheel; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# EasyMANET CLI
|
|
30
|
+
|
|
31
|
+
Public installable CLI and automation surface for EasyMANET.
|
|
32
|
+
|
|
33
|
+
This repository is generated from `the-Drunken-coder/easymanet`. Its job is to
|
|
34
|
+
publish the command-line tool that validates fleet files, renders node
|
|
35
|
+
provisioning payloads, lists disks, downloads or builds images, flashes media,
|
|
36
|
+
and exposes diagnostics-friendly workflows for humans, scripts, and local AI
|
|
37
|
+
agents.
|
|
38
|
+
|
|
39
|
+
## Install From Source
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m pip install -e ".[dev]"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Common Commands
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
easymanet validate --config fleet.yml
|
|
49
|
+
easymanet render --config fleet.yml --node point01
|
|
50
|
+
easymanet disks
|
|
51
|
+
easymanet flash --config fleet.yml --node point01 --device /dev/disk4 --base-image ./image.img.gz --dry-run
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Release Flow
|
|
55
|
+
|
|
56
|
+
The tiny bootstrap workflow accepts an intentional `repository_dispatch` or
|
|
57
|
+
manual trigger, then invokes the larger CLI release workflow. The release
|
|
58
|
+
workflow runs tests, builds the wheel and source distribution, uploads them as
|
|
59
|
+
artifacts, and creates a GitHub Release when a release tag is supplied.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# EasyMANET CLI
|
|
2
|
+
|
|
3
|
+
Public installable CLI and automation surface for EasyMANET.
|
|
4
|
+
|
|
5
|
+
This repository is generated from `the-Drunken-coder/easymanet`. Its job is to
|
|
6
|
+
publish the command-line tool that validates fleet files, renders node
|
|
7
|
+
provisioning payloads, lists disks, downloads or builds images, flashes media,
|
|
8
|
+
and exposes diagnostics-friendly workflows for humans, scripts, and local AI
|
|
9
|
+
agents.
|
|
10
|
+
|
|
11
|
+
## Install From Source
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python -m pip install -e ".[dev]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Common Commands
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
easymanet validate --config fleet.yml
|
|
21
|
+
easymanet render --config fleet.yml --node point01
|
|
22
|
+
easymanet disks
|
|
23
|
+
easymanet flash --config fleet.yml --node point01 --device /dev/disk4 --base-image ./image.img.gz --dry-run
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Release Flow
|
|
27
|
+
|
|
28
|
+
The tiny bootstrap workflow accepts an intentional `repository_dispatch` or
|
|
29
|
+
manual trigger, then invokes the larger CLI release workflow. The release
|
|
30
|
+
workflow runs tests, builds the wheel and source distribution, uploads them as
|
|
31
|
+
artifacts, and creates a GitHub Release when a release tag is supplied.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Installable EasyMANET CLI surface."""
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""EasyMANET CLI — zero-touch OpenMANET provisioning and imaging.
|
|
2
|
+
|
|
3
|
+
Commands:
|
|
4
|
+
easymanet disks List removable disks
|
|
5
|
+
easymanet validate --config FILE Validate fleet config
|
|
6
|
+
easymanet render --config FILE Render resolved provision.json
|
|
7
|
+
easymanet flash --config FILE ... Flash an image and stage node config
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
from easymanet.disks import list_disks
|
|
15
|
+
from easymanet.manifest import ManifestError, load_manifest
|
|
16
|
+
from easymanet.platform import check_platform
|
|
17
|
+
from easymanet.render import render
|
|
18
|
+
from easymanet.validate import validate
|
|
19
|
+
from easymanet.workspace import (
|
|
20
|
+
ensure_workspace,
|
|
21
|
+
fleet_file_records,
|
|
22
|
+
fleets_dir,
|
|
23
|
+
resolve_fleet_config,
|
|
24
|
+
workspace_payload,
|
|
25
|
+
)
|
|
26
|
+
from easymanet_image.cli import register_image_commands
|
|
27
|
+
|
|
28
|
+
from .common import print_errors_and_warnings
|
|
29
|
+
from .flash import register_flash_command
|
|
30
|
+
|
|
31
|
+
app = typer.Typer(
|
|
32
|
+
name="easymanet",
|
|
33
|
+
help="Zero-touch OpenMANET provisioning and imaging",
|
|
34
|
+
no_args_is_help=True,
|
|
35
|
+
)
|
|
36
|
+
image_app = typer.Typer(help="Manage image URLs, cache, and firmware builds")
|
|
37
|
+
app.add_typer(image_app, name="image")
|
|
38
|
+
|
|
39
|
+
register_flash_command(app)
|
|
40
|
+
register_image_commands(image_app)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command(name="validate")
|
|
44
|
+
def validate_cmd(
|
|
45
|
+
config: str = typer.Option(
|
|
46
|
+
..., "--config", "-c", help="Path to fleet.yml config file"
|
|
47
|
+
),
|
|
48
|
+
node: Optional[str] = typer.Option(
|
|
49
|
+
None, "--node", "-n", help="Validate a specific node"
|
|
50
|
+
),
|
|
51
|
+
):
|
|
52
|
+
"""Validate a fleet.yml config file."""
|
|
53
|
+
config_path = resolve_fleet_config(config)
|
|
54
|
+
try:
|
|
55
|
+
manifest = load_manifest(str(config_path))
|
|
56
|
+
except ManifestError as e:
|
|
57
|
+
typer.secho(f"Error: {e}", fg=typer.colors.RED)
|
|
58
|
+
raise typer.Exit(1) from e
|
|
59
|
+
|
|
60
|
+
result = validate(manifest, node_name=node)
|
|
61
|
+
typer.secho(f"Validating: {config_path}", bold=True)
|
|
62
|
+
if node:
|
|
63
|
+
typer.secho(f"Selected node: {node}")
|
|
64
|
+
|
|
65
|
+
exit_code = print_errors_and_warnings(result)
|
|
66
|
+
raise typer.Exit(exit_code)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@app.command(name="render")
|
|
70
|
+
def render_cmd(
|
|
71
|
+
config: str = typer.Option(
|
|
72
|
+
..., "--config", "-c", help="Path to fleet.yml config file"
|
|
73
|
+
),
|
|
74
|
+
node: str = typer.Option(
|
|
75
|
+
..., "--node", "-n", help="Node name to render resolved config for"
|
|
76
|
+
),
|
|
77
|
+
):
|
|
78
|
+
"""Render the resolved provision.json for a node."""
|
|
79
|
+
config_path = resolve_fleet_config(config)
|
|
80
|
+
try:
|
|
81
|
+
manifest = load_manifest(str(config_path))
|
|
82
|
+
except ManifestError as e:
|
|
83
|
+
typer.secho(f"Error: {e}", fg=typer.colors.RED)
|
|
84
|
+
raise typer.Exit(1) from e
|
|
85
|
+
|
|
86
|
+
result = validate(manifest, node_name=node)
|
|
87
|
+
if result.errors:
|
|
88
|
+
typer.secho("Config has validation errors:", fg=typer.colors.RED)
|
|
89
|
+
for e in result.errors:
|
|
90
|
+
typer.secho(f" ✗ {e}", fg=typer.colors.RED)
|
|
91
|
+
raise typer.Exit(1)
|
|
92
|
+
|
|
93
|
+
output = render(manifest, node)
|
|
94
|
+
print(output)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command(name="init")
|
|
98
|
+
def init_cmd():
|
|
99
|
+
"""Create the shared EasyMANET workspace in Documents."""
|
|
100
|
+
payload = workspace_payload()
|
|
101
|
+
typer.secho("EasyMANET workspace ready.", fg=typer.colors.GREEN)
|
|
102
|
+
typer.echo(f" Root: {payload['root']}")
|
|
103
|
+
typer.echo(f" Fleets: {payload['fleets_dir']}")
|
|
104
|
+
typer.echo(f" Images: {payload['images_dir']}")
|
|
105
|
+
typer.echo(f" Diagnostics: {payload['diagnostics_dir']}")
|
|
106
|
+
typer.echo(f" Builds: {payload['builds_dir']}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command(name="fleets")
|
|
110
|
+
def fleets_cmd():
|
|
111
|
+
"""List fleet files in the shared EasyMANET workspace."""
|
|
112
|
+
records = fleet_file_records()
|
|
113
|
+
typer.echo(f"Fleets folder: {fleets_dir()}")
|
|
114
|
+
if not records:
|
|
115
|
+
typer.secho("No fleet files found.", fg=typer.colors.YELLOW)
|
|
116
|
+
typer.echo("Add .yml or .yaml files to the Fleets folder above.")
|
|
117
|
+
return
|
|
118
|
+
for record in records:
|
|
119
|
+
typer.echo(f" {record['relative_path']}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@app.command(name="disks")
|
|
123
|
+
def disks_cmd(
|
|
124
|
+
all_disks: bool = typer.Option(
|
|
125
|
+
False,
|
|
126
|
+
"--all",
|
|
127
|
+
help="List every block device, not only removable/USB/MMC (Linux) or external (macOS)",
|
|
128
|
+
),
|
|
129
|
+
):
|
|
130
|
+
"""List available disks for flashing."""
|
|
131
|
+
check_platform()
|
|
132
|
+
disks = list_disks(include_all=all_disks)
|
|
133
|
+
|
|
134
|
+
if not disks:
|
|
135
|
+
msg = "No disks found." if all_disks else "No removable/external disks found."
|
|
136
|
+
typer.secho(msg, fg=typer.colors.YELLOW)
|
|
137
|
+
if not all_disks:
|
|
138
|
+
typer.echo("Use --all to include every block device.")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
for d in disks:
|
|
142
|
+
removable = "yes" if d.removable else "no"
|
|
143
|
+
mounted_str = ", ".join(d.mounted) if d.mounted else "(none)"
|
|
144
|
+
typer.echo(f" {d.device}")
|
|
145
|
+
typer.echo(f" Model: {d.model}")
|
|
146
|
+
typer.echo(f" Size: {d.size_human}")
|
|
147
|
+
typer.echo(f" Removable: {removable}")
|
|
148
|
+
typer.echo(f" Mounted: {mounted_str}")
|
|
149
|
+
|
|
150
|
+
for w in d.warnings:
|
|
151
|
+
typer.secho(f" {w}", fg=typer.colors.RED)
|
|
152
|
+
|
|
153
|
+
typer.echo()
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def main():
|
|
157
|
+
ensure_workspace()
|
|
158
|
+
app()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Shared CLI helpers."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from easymanet import __version__
|
|
9
|
+
from easymanet.download import check_easymanet_update
|
|
10
|
+
from easymanet.validate import ValidationResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def print_header(text: str) -> None:
|
|
14
|
+
typer.secho(text, bold=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def maybe_show_update_notice() -> None:
|
|
18
|
+
if os.environ.get("EASYMANET_SKIP_UPDATE_CHECK", "").strip().lower() in {
|
|
19
|
+
"1",
|
|
20
|
+
"true",
|
|
21
|
+
"yes",
|
|
22
|
+
}:
|
|
23
|
+
return
|
|
24
|
+
update = check_easymanet_update()
|
|
25
|
+
if update:
|
|
26
|
+
typer.secho(
|
|
27
|
+
f"EasyMANET {update} is available (you have {__version__}). "
|
|
28
|
+
f"Run: {_upgrade_command()}",
|
|
29
|
+
fg=typer.colors.YELLOW,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _upgrade_command() -> str:
|
|
34
|
+
in_venv = bool(os.environ.get("VIRTUAL_ENV")) or (
|
|
35
|
+
getattr(sys, "base_prefix", sys.prefix) != sys.prefix
|
|
36
|
+
)
|
|
37
|
+
if in_venv:
|
|
38
|
+
return "pip3 install --upgrade easymanet"
|
|
39
|
+
return "pipx upgrade easymanet"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def print_errors_and_warnings(result: ValidationResult) -> int:
|
|
43
|
+
exit_code = 0
|
|
44
|
+
if result.errors:
|
|
45
|
+
typer.secho(f"\n{len(result.errors)} error(s):", fg=typer.colors.RED)
|
|
46
|
+
for e in result.errors:
|
|
47
|
+
typer.secho(f" ✗ {e}", fg=typer.colors.RED)
|
|
48
|
+
exit_code = 1
|
|
49
|
+
if result.warnings:
|
|
50
|
+
typer.secho(f"\n{len(result.warnings)} warning(s):", fg=typer.colors.YELLOW)
|
|
51
|
+
for w in result.warnings:
|
|
52
|
+
typer.secho(f" ⚠ {w}", fg=typer.colors.YELLOW)
|
|
53
|
+
if result.valid and not result.warnings:
|
|
54
|
+
typer.secho("✓ Config is valid", fg=typer.colors.GREEN)
|
|
55
|
+
elif result.valid:
|
|
56
|
+
typer.secho("✓ Config is valid (with warnings)", fg=typer.colors.GREEN)
|
|
57
|
+
return exit_code
|