redeploy 0.2.28__tar.gz → 0.2.30__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.
- {redeploy-0.2.28 → redeploy-0.2.30}/PKG-INFO +6 -6
- {redeploy-0.2.28 → redeploy-0.2.30}/README.md +5 -5
- {redeploy-0.2.28 → redeploy-0.2.30}/pyproject.toml +1 -1
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/__init__.py +1 -1
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/devices.py +14 -4
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/discovery.py +44 -3
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/PKG-INFO +6 -6
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/exceptions.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/executor.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/handlers.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/progress.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/rollback.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/apply/state.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/audit.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/audit.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/detect.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/diagnose.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/diff.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/exec_.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/export.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/import_.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/init.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/inspect.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/patterns.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/plan_apply.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/plugin.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/probe.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/state.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/status.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/target.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/version.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/commands/workflow.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/core.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli/display.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/cli.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/data_sync.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/detector.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/probes.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/remote.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/templates.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/detect/workflow.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl/loader.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl/parser.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/context.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/decorators.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/docker_steps.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/exceptions.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/runner.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/dsl_python/steps.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/fleet.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/base.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/docker_compose.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/parsers/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/parsers/compose.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/iac/registry.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/compiler.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/models.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/markpact/parser.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/models.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/observe.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/parse.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/patterns.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plan/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plan/planner.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/browser_reload.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/notify.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/plugins/builtin/systemd_reload.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/spec_loader.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/ssh.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/steps.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_audit.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_cli.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_data_sync.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_discovery.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_dsl.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_examples.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_executor.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_fleet.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_git_integration.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_iac.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_manifest.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_markpact_compiler.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_markpact_parser.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_models.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_observe.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_parse.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_patterns.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_planner_edge.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_plugins.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_probes.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_public_api.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_resume.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_spec_loader.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_ssh.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_templates.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_verify.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_changelog.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_cli.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_manifest.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_version_sources.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/tests/test_workflow.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/verify.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/bump.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/changelog.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/commits.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/diff.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/git_integration.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/git_transaction.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/manifest.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/__init__.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/base.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/json_.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/plain.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/regex.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/toml_.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/sources/yaml_.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version/transaction.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy/version.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/SOURCES.txt +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/dependency_links.txt +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/entry_points.txt +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/requires.txt +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/redeploy.egg-info/top_level.txt +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/setup.cfg +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/tests/test_iac.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/tests/test_parse.py +0 -0
- {redeploy-0.2.28 → redeploy-0.2.30}/tests/test_redeploy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redeploy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.30
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -27,17 +27,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
|
27
27
|
|
|
28
28
|
## AI Cost Tracking
|
|
29
29
|
|
|
30
|
-
    
|
|
31
|
+
  
|
|
32
32
|
|
|
33
|
-
- 🤖 **LLM usage:** $7.
|
|
34
|
-
- 👤 **Human dev:** ~$
|
|
33
|
+
- 🤖 **LLM usage:** $7.5000 (50 commits)
|
|
34
|
+
- 👤 **Human dev:** ~$1505 (15.0h @ $100/h, 30min dedup)
|
|
35
35
|
|
|
36
36
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
    
|
|
41
41
|
|
|
42
42
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
43
43
|
|
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
    
|
|
7
|
+
  
|
|
8
8
|
|
|
9
|
-
- 🤖 **LLM usage:** $7.
|
|
10
|
-
- 👤 **Human dev:** ~$
|
|
9
|
+
- 🤖 **LLM usage:** $7.5000 (50 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$1505 (15.0h @ $100/h, 30min dedup)
|
|
11
11
|
|
|
12
12
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
    
|
|
17
17
|
|
|
18
18
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
19
19
|
|
|
@@ -7,7 +7,7 @@ Public API (stable from 0.2.0, semver guaranteed)::
|
|
|
7
7
|
|
|
8
8
|
Everything not listed in ``__all__`` is internal and may change without notice.
|
|
9
9
|
"""
|
|
10
|
-
__version__ = "0.2.
|
|
10
|
+
__version__ = "0.2.30"
|
|
11
11
|
|
|
12
12
|
# ── Core models ───────────────────────────────────────────────────────────────
|
|
13
13
|
from .models import ( # noqa: F401
|
|
@@ -13,9 +13,10 @@ from rich.table import Table
|
|
|
13
13
|
@click.command()
|
|
14
14
|
@click.option("--tag", default=None, help="Filter by tag")
|
|
15
15
|
@click.option("--strategy", default=None, help="Filter by strategy")
|
|
16
|
+
@click.option("--rpi", is_flag=True, help="Show only Raspberry Pi devices")
|
|
16
17
|
@click.option("--reachable", is_flag=True, help="Show only recently-seen devices")
|
|
17
18
|
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
18
|
-
def devices(tag, strategy, reachable, as_json):
|
|
19
|
+
def devices(tag, strategy, rpi, reachable, as_json):
|
|
19
20
|
"""List known devices from ~/.config/redeploy/devices.yaml.
|
|
20
21
|
|
|
21
22
|
\b
|
|
@@ -35,6 +36,8 @@ def devices(tag, strategy, reachable, as_json):
|
|
|
35
36
|
devs = [d for d in devs if tag in d.tags]
|
|
36
37
|
if strategy:
|
|
37
38
|
devs = [d for d in devs if d.strategy == strategy]
|
|
39
|
+
if rpi:
|
|
40
|
+
devs = [d for d in devs if "raspberry-pi" in d.tags]
|
|
38
41
|
if reachable:
|
|
39
42
|
devs = [d for d in devs if d.is_reachable]
|
|
40
43
|
|
|
@@ -64,12 +67,16 @@ def devices(tag, strategy, reachable, as_json):
|
|
|
64
67
|
for d in devs:
|
|
65
68
|
seen = d.last_seen.strftime("%m-%d %H:%M") if d.last_seen else "never"
|
|
66
69
|
ssh = "[green]✓[/green]" if d.last_ssh_ok else "[red]✗[/red]"
|
|
70
|
+
tags_str = ",".join(d.tags) or "—"
|
|
71
|
+
# Highlight raspberry-pi tag
|
|
72
|
+
if "raspberry-pi" in d.tags:
|
|
73
|
+
tags_str = tags_str.replace("raspberry-pi", "[bold magenta]raspberry-pi[/bold magenta]")
|
|
67
74
|
t.add_row(
|
|
68
75
|
d.id,
|
|
69
76
|
d.host,
|
|
70
77
|
d.strategy,
|
|
71
78
|
d.app or "—",
|
|
72
|
-
|
|
79
|
+
tags_str,
|
|
73
80
|
seen,
|
|
74
81
|
ssh,
|
|
75
82
|
)
|
|
@@ -130,17 +137,20 @@ def scan(subnet, ssh_users, ssh_port, ping, no_mdns, timeout, no_save):
|
|
|
130
137
|
)
|
|
131
138
|
|
|
132
139
|
ssh_ok = [h for h in found if h.ssh_ok]
|
|
133
|
-
|
|
140
|
+
rpi_count = sum(1 for h in found if h.is_raspberry_pi)
|
|
141
|
+
console.print(f" found {len(found)} host(s), {len(ssh_ok)} SSH-accessible, {rpi_count} Raspberry Pi\n")
|
|
134
142
|
|
|
135
143
|
t = Table(show_header=True, box=None, padding=(0, 2))
|
|
136
144
|
t.add_column("IP")
|
|
137
145
|
t.add_column("Hostname", style="dim")
|
|
138
146
|
t.add_column("MAC", style="dim")
|
|
139
147
|
t.add_column("SSH user", style="cyan")
|
|
148
|
+
t.add_column("RPi", style="bold magenta")
|
|
140
149
|
t.add_column("Source", style="dim")
|
|
141
150
|
for h in found:
|
|
142
151
|
ssh_col = f"[green]{h.ssh_user}[/green]" if h.ssh_ok else "[red]✗[/red]"
|
|
143
|
-
|
|
152
|
+
rpi_col = "🍓" if h.is_raspberry_pi else "—"
|
|
153
|
+
t.add_row(h.ip, h.hostname or "—", h.mac or "—", ssh_col, rpi_col, h.source)
|
|
144
154
|
console.print(t)
|
|
145
155
|
|
|
146
156
|
if not no_save and ssh_ok:
|
|
@@ -38,6 +38,24 @@ class DiscoveredHost:
|
|
|
38
38
|
ssh_user: str = ""
|
|
39
39
|
source: str = "unknown"
|
|
40
40
|
ports_open: list[int] = field(default_factory=list)
|
|
41
|
+
is_raspberry_pi: bool = False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ── Raspberry Pi identification ─────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
_RPI_MAC_PREFIXES = [
|
|
47
|
+
"b8:27:eb", # Raspberry Pi Foundation
|
|
48
|
+
"dc:a6:32", # Raspberry Pi Trading
|
|
49
|
+
"e4:5f:01", # Raspberry Pi Trading
|
|
50
|
+
"28:cd:c1", # Raspberry Pi 5
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
def _is_raspberry_pi_mac(mac: str) -> bool:
|
|
54
|
+
"""Check if MAC address belongs to Raspberry Pi."""
|
|
55
|
+
if not mac:
|
|
56
|
+
return False
|
|
57
|
+
mac_prefix = mac[:8].lower()
|
|
58
|
+
return mac_prefix in _RPI_MAC_PREFIXES
|
|
41
59
|
|
|
42
60
|
|
|
43
61
|
# ── Individual scanners ───────────────────────────────────────────────────────
|
|
@@ -89,7 +107,12 @@ def _scan_arp_cache() -> list[DiscoveredHost]:
|
|
|
89
107
|
parts = line.split()
|
|
90
108
|
if len(parts) >= 5 and re.match(r"\d+\.\d+\.\d+\.\d+", parts[0]):
|
|
91
109
|
mac = parts[4] if len(parts) > 4 else ""
|
|
92
|
-
results.append(DiscoveredHost(
|
|
110
|
+
results.append(DiscoveredHost(
|
|
111
|
+
ip=parts[0],
|
|
112
|
+
mac=mac,
|
|
113
|
+
source="arp",
|
|
114
|
+
is_raspberry_pi=_is_raspberry_pi_mac(mac)
|
|
115
|
+
))
|
|
93
116
|
if results:
|
|
94
117
|
logger.debug(f"arp (ip neigh): {len(results)} hosts")
|
|
95
118
|
return results
|
|
@@ -101,7 +124,13 @@ def _scan_arp_cache() -> list[DiscoveredHost]:
|
|
|
101
124
|
# ? (192.168.1.1) at aa:bb:cc:dd:ee:ff [ether] on eth0
|
|
102
125
|
m = re.search(r"\((\d+\.\d+\.\d+\.\d+)\).*?([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f:]+)", line, re.I)
|
|
103
126
|
if m:
|
|
104
|
-
|
|
127
|
+
mac = m.group(2)
|
|
128
|
+
results.append(DiscoveredHost(
|
|
129
|
+
ip=m.group(1),
|
|
130
|
+
mac=mac,
|
|
131
|
+
source="arp",
|
|
132
|
+
is_raspberry_pi=_is_raspberry_pi_mac(mac)
|
|
133
|
+
))
|
|
105
134
|
logger.debug(f"arp: {len(results)} hosts")
|
|
106
135
|
return results
|
|
107
136
|
|
|
@@ -250,6 +279,8 @@ def _merge(hosts: list[DiscoveredHost]) -> list[DiscoveredHost]:
|
|
|
250
279
|
existing.ssh_user = existing.ssh_user or h.ssh_user
|
|
251
280
|
if existing.source == "unknown":
|
|
252
281
|
existing.source = h.source
|
|
282
|
+
if h.is_raspberry_pi:
|
|
283
|
+
existing.is_raspberry_pi = True
|
|
253
284
|
else:
|
|
254
285
|
by_ip[h.ip] = h
|
|
255
286
|
return list(by_ip.values())
|
|
@@ -302,6 +333,11 @@ def discover(
|
|
|
302
333
|
|
|
303
334
|
found = _merge(found)
|
|
304
335
|
|
|
336
|
+
# Log Raspberry Pi devices
|
|
337
|
+
rpi_devices = [h for h in found if h.is_raspberry_pi]
|
|
338
|
+
if rpi_devices:
|
|
339
|
+
logger.info(f"Found {len(rpi_devices)} Raspberry Pi device(s): {', '.join(h.ip for h in rpi_devices)}")
|
|
340
|
+
|
|
305
341
|
# SSH probe
|
|
306
342
|
if probe_ssh and found:
|
|
307
343
|
logger.info(f"SSH probe: {len(found)} hosts ({timeout}s timeout each)")
|
|
@@ -339,9 +375,14 @@ def update_registry(
|
|
|
339
375
|
existing.hostname = h.hostname
|
|
340
376
|
if h.ssh_ok:
|
|
341
377
|
existing.last_ssh_ok = now
|
|
378
|
+
if h.is_raspberry_pi and "raspberry-pi" not in existing.tags:
|
|
379
|
+
existing.tags.append("raspberry-pi")
|
|
342
380
|
reg.upsert(existing)
|
|
343
381
|
elif h.ssh_ok:
|
|
344
382
|
# Only auto-add SSH-accessible devices
|
|
383
|
+
tags = ["discovered"]
|
|
384
|
+
if h.is_raspberry_pi:
|
|
385
|
+
tags.append("raspberry-pi")
|
|
345
386
|
reg.upsert(KnownDevice(
|
|
346
387
|
id=device_id,
|
|
347
388
|
host=device_id,
|
|
@@ -352,7 +393,7 @@ def update_registry(
|
|
|
352
393
|
last_seen=now,
|
|
353
394
|
last_ssh_ok=now,
|
|
354
395
|
source=h.source,
|
|
355
|
-
tags=
|
|
396
|
+
tags=tags,
|
|
356
397
|
))
|
|
357
398
|
|
|
358
399
|
if save:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redeploy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.30
|
|
4
4
|
Summary: Infrastructure migration toolkit: detect → plan → apply
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -27,17 +27,17 @@ Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
|
27
27
|
|
|
28
28
|
## AI Cost Tracking
|
|
29
29
|
|
|
30
|
-
    
|
|
31
|
+
  
|
|
32
32
|
|
|
33
|
-
- 🤖 **LLM usage:** $7.
|
|
34
|
-
- 👤 **Human dev:** ~$
|
|
33
|
+
- 🤖 **LLM usage:** $7.5000 (50 commits)
|
|
34
|
+
- 👤 **Human dev:** ~$1505 (15.0h @ $100/h, 30min dedup)
|
|
35
35
|
|
|
36
36
|
Generated on 2026-04-21 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
    
|
|
41
41
|
|
|
42
42
|
Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
|
|
43
43
|
|
|
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
|
|
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
|