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.
Files changed (63) hide show
  1. easymanet-0.1.0/PKG-INFO +59 -0
  2. easymanet-0.1.0/README.md +31 -0
  3. easymanet-0.1.0/apps/cli/src/easymanet_cli/__init__.py +1 -0
  4. easymanet-0.1.0/apps/cli/src/easymanet_cli/app.py +162 -0
  5. easymanet-0.1.0/apps/cli/src/easymanet_cli/common.py +57 -0
  6. easymanet-0.1.0/apps/cli/src/easymanet_cli/flash.py +437 -0
  7. easymanet-0.1.0/easymanet.egg-info/PKG-INFO +59 -0
  8. easymanet-0.1.0/easymanet.egg-info/SOURCES.txt +61 -0
  9. easymanet-0.1.0/easymanet.egg-info/dependency_links.txt +1 -0
  10. easymanet-0.1.0/easymanet.egg-info/entry_points.txt +2 -0
  11. easymanet-0.1.0/easymanet.egg-info/requires.txt +10 -0
  12. easymanet-0.1.0/easymanet.egg-info/top_level.txt +3 -0
  13. easymanet-0.1.0/images/openmanet/provisioning/extra-packages.txt +6 -0
  14. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/README.md +18 -0
  15. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/easymanet/provision.json +3 -0
  16. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/init.d/easymanet-boot-report +13 -0
  17. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/init.d/easymanet-management-lan +22 -0
  18. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/sysctl.d/99-easymanet.conf +18 -0
  19. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/uci-defaults/97-easymanet-management-lan +7 -0
  20. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/uci-defaults/98-easymanet-boot-report +8 -0
  21. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/etc/uci-defaults/99-easymanet +24 -0
  22. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/boot-report.sh +125 -0
  23. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/network.sh +86 -0
  24. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/provision-lib.sh +75 -0
  25. easymanet-0.1.0/images/openmanet/provisioning/openwrt-overlay/usr/lib/easymanet/provision.sh +532 -0
  26. easymanet-0.1.0/packages/core/src/easymanet/__init__.py +3 -0
  27. easymanet-0.1.0/packages/core/src/easymanet/disks/__init__.py +73 -0
  28. easymanet-0.1.0/packages/core/src/easymanet/disks/_common.py +88 -0
  29. easymanet-0.1.0/packages/core/src/easymanet/disks/core.py +139 -0
  30. easymanet-0.1.0/packages/core/src/easymanet/disks/linux.py +306 -0
  31. easymanet-0.1.0/packages/core/src/easymanet/disks/macos.py +343 -0
  32. easymanet-0.1.0/packages/core/src/easymanet/download.py +563 -0
  33. easymanet-0.1.0/packages/core/src/easymanet/format.py +13 -0
  34. easymanet-0.1.0/packages/core/src/easymanet/image.py +323 -0
  35. easymanet-0.1.0/packages/core/src/easymanet/inject.py +322 -0
  36. easymanet-0.1.0/packages/core/src/easymanet/manifest.py +91 -0
  37. easymanet-0.1.0/packages/core/src/easymanet/platform.py +25 -0
  38. easymanet-0.1.0/packages/core/src/easymanet/privileges.py +39 -0
  39. easymanet-0.1.0/packages/core/src/easymanet/render.py +60 -0
  40. easymanet-0.1.0/packages/core/src/easymanet/validate.py +338 -0
  41. easymanet-0.1.0/packages/core/src/easymanet/workspace.py +132 -0
  42. easymanet-0.1.0/packages/image/src/easymanet_image/__init__.py +1 -0
  43. easymanet-0.1.0/packages/image/src/easymanet_image/build.py +410 -0
  44. easymanet-0.1.0/packages/image/src/easymanet_image/cli.py +240 -0
  45. easymanet-0.1.0/packages/image/src/easymanet_image/release.py +105 -0
  46. easymanet-0.1.0/pyproject.toml +94 -0
  47. easymanet-0.1.0/setup.cfg +4 -0
  48. easymanet-0.1.0/tests/test_build.py +249 -0
  49. easymanet-0.1.0/tests/test_cli.py +383 -0
  50. easymanet-0.1.0/tests/test_cli_common.py +17 -0
  51. easymanet-0.1.0/tests/test_disks.py +543 -0
  52. easymanet-0.1.0/tests/test_download.py +467 -0
  53. easymanet-0.1.0/tests/test_extra_packages.py +22 -0
  54. easymanet-0.1.0/tests/test_firstboot.py +147 -0
  55. easymanet-0.1.0/tests/test_image.py +508 -0
  56. easymanet-0.1.0/tests/test_inject.py +248 -0
  57. easymanet-0.1.0/tests/test_manifest.py +153 -0
  58. easymanet-0.1.0/tests/test_packaging.py +129 -0
  59. easymanet-0.1.0/tests/test_privileges.py +41 -0
  60. easymanet-0.1.0/tests/test_provision_behavior.py +508 -0
  61. easymanet-0.1.0/tests/test_render.py +217 -0
  62. easymanet-0.1.0/tests/test_sysctl.py +18 -0
  63. easymanet-0.1.0/tests/test_validate.py +418 -0
@@ -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