@mriekki/lean 0.1.0 → 0.2.0
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.
- package/README.md +71 -67
- package/assets/components.json +33 -33
- package/assets/core/distribution.json +52 -51
- package/assets/deployment/distribution.json +34 -34
- package/assets/generated/ASSEMBLY.json +38 -33
- package/assets/generated/core/core/skills/core-sync/SKILL.md +78 -120
- package/assets/generated/core/core/skills/deploy/SKILL.md +74 -91
- package/assets/generated/core/core/skills/lean-update/SKILL.md +189 -0
- package/assets/generated/core/core/skills/upstream-fix/SKILL.md +91 -76
- package/assets/generated/deployment/distribution/lean-poc-deploy.py +8 -8
- package/assets/generated/deployment/src/lean_poc_deployment/__init__.py +3 -3
- package/assets/generated/deployment/src/lean_poc_deployment/__main__.py +3 -3
- package/assets/generated/deployment/src/lean_poc_deployment/app_cli.py +140 -140
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime.py +455 -455
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime_remote.py +415 -415
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform.py +208 -208
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_assets.py +100 -100
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_remote.py +271 -271
- package/assets/generated/deployment/src/lean_poc_deployment/cli.py +437 -437
- package/assets/generated/deployment/src/lean_poc_deployment/doctor.py +121 -121
- package/assets/generated/deployment/src/lean_poc_deployment/entrypoint.py +25 -25
- package/assets/generated/deployment/src/lean_poc_deployment/errors.py +5 -5
- package/assets/generated/deployment/src/lean_poc_deployment/keys.py +171 -171
- package/assets/generated/deployment/src/lean_poc_deployment/profiles.py +204 -204
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry.py +367 -367
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry_remote.py +443 -443
- package/assets/generated/deployment/src/lean_poc_deployment/security.py +290 -290
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter.py +334 -334
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter_remote.py +339 -339
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_cli.py +85 -85
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_import_recovery.py +437 -437
- package/assets/generated/deployment/src/lean_poc_deployment/ssh.py +118 -118
- package/assets/generated/deployment/src/lean_poc_deployment/teardown.py +286 -286
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_cli.py +120 -120
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_remote.py +100 -100
- package/assets/generated/deployment/src/lean_poc_deployment/trust.py +278 -278
- package/bin/lean.mjs +9 -9
- package/package.json +33 -33
- package/src/cli.mjs +131 -131
- package/src/engine.mjs +243 -243
- package/src/package-data.mjs +18 -18
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
"""Strict SSH verification primitives for logical targets."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import shutil
|
|
6
|
-
import socket
|
|
7
|
-
import subprocess
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
from .errors import DeploymentKitError
|
|
12
|
-
from .security import check_ssh_material_security, validate_target_trust_file
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def required_tool(name: str) -> str:
|
|
16
|
-
path = shutil.which(name)
|
|
17
|
-
if path is None:
|
|
18
|
-
raise DeploymentKitError(f"Required OpenSSH tool is unavailable: {name}")
|
|
19
|
-
return path
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def resolve_addresses(profile: dict[str, Any]) -> list[str]:
|
|
23
|
-
override = profile["addressOverride"]
|
|
24
|
-
if override:
|
|
25
|
-
return [override]
|
|
26
|
-
try:
|
|
27
|
-
answers = socket.getaddrinfo(
|
|
28
|
-
profile["hostName"],
|
|
29
|
-
profile["port"],
|
|
30
|
-
type=socket.SOCK_STREAM,
|
|
31
|
-
)
|
|
32
|
-
except OSError as error:
|
|
33
|
-
raise DeploymentKitError(
|
|
34
|
-
f"Cannot resolve target hostname {profile['hostName']}: {error}. "
|
|
35
|
-
"Use only a separately verified address override; do not scan the network."
|
|
36
|
-
) from error
|
|
37
|
-
addresses = sorted({answer[4][0] for answer in answers})
|
|
38
|
-
if not addresses:
|
|
39
|
-
raise DeploymentKitError(f"Target hostname {profile['hostName']} returned no addresses.")
|
|
40
|
-
return addresses
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def ssh_args(profile: dict[str, Any], address: str, *, command: str = "true") -> list[str]:
|
|
44
|
-
return [
|
|
45
|
-
required_tool("ssh"),
|
|
46
|
-
"-F",
|
|
47
|
-
"none",
|
|
48
|
-
"-o",
|
|
49
|
-
"BatchMode=yes",
|
|
50
|
-
"-o",
|
|
51
|
-
"ConnectTimeout=8",
|
|
52
|
-
"-o",
|
|
53
|
-
"StrictHostKeyChecking=yes",
|
|
54
|
-
"-o",
|
|
55
|
-
"UpdateHostKeys=no",
|
|
56
|
-
"-o",
|
|
57
|
-
"CheckHostIP=no",
|
|
58
|
-
"-o",
|
|
59
|
-
"VerifyHostKeyDNS=no",
|
|
60
|
-
"-o",
|
|
61
|
-
"GlobalKnownHostsFile=none",
|
|
62
|
-
"-o",
|
|
63
|
-
f"UserKnownHostsFile={profile['knownHostsFile']}",
|
|
64
|
-
"-o",
|
|
65
|
-
f"HostKeyAlias={profile['sshAlias']}",
|
|
66
|
-
"-o",
|
|
67
|
-
f"IdentityFile={profile['identityFile']}",
|
|
68
|
-
"-o",
|
|
69
|
-
"IdentitiesOnly=yes",
|
|
70
|
-
"-o",
|
|
71
|
-
"PasswordAuthentication=no",
|
|
72
|
-
"-o",
|
|
73
|
-
"KbdInteractiveAuthentication=no",
|
|
74
|
-
"-o",
|
|
75
|
-
"PreferredAuthentications=publickey",
|
|
76
|
-
"-o",
|
|
77
|
-
"ForwardAgent=no",
|
|
78
|
-
"-o",
|
|
79
|
-
"ForwardX11=no",
|
|
80
|
-
"-o",
|
|
81
|
-
"ClearAllForwardings=yes",
|
|
82
|
-
"-p",
|
|
83
|
-
str(profile["port"]),
|
|
84
|
-
f"{profile['user']}@{address}",
|
|
85
|
-
command,
|
|
86
|
-
]
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def verify_access(profile: dict[str, Any]) -> dict[str, str]:
|
|
90
|
-
identity = Path(profile["identityFile"])
|
|
91
|
-
known_hosts = Path(profile["knownHostsFile"])
|
|
92
|
-
if not identity.is_file():
|
|
93
|
-
raise DeploymentKitError(f"Dedicated private key is missing: {identity}")
|
|
94
|
-
if not known_hosts.is_file() or known_hosts.stat().st_size == 0:
|
|
95
|
-
raise DeploymentKitError(
|
|
96
|
-
f"Target-specific trust file is missing or empty: {known_hosts}. "
|
|
97
|
-
"Enroll trust only after independently verifying the host fingerprint."
|
|
98
|
-
)
|
|
99
|
-
check_ssh_material_security(identity, known_hosts)
|
|
100
|
-
validate_target_trust_file(profile)
|
|
101
|
-
|
|
102
|
-
failures: list[str] = []
|
|
103
|
-
for address in resolve_addresses(profile):
|
|
104
|
-
result = subprocess.run(
|
|
105
|
-
ssh_args(profile, address),
|
|
106
|
-
check=False,
|
|
107
|
-
capture_output=True,
|
|
108
|
-
text=True,
|
|
109
|
-
encoding="utf-8",
|
|
110
|
-
errors="replace",
|
|
111
|
-
)
|
|
112
|
-
if result.returncode == 0:
|
|
113
|
-
return {"targetName": profile["targetName"], "address": address, "status": "verified"}
|
|
114
|
-
failures.append(f"{address}: exit {result.returncode}")
|
|
115
|
-
|
|
116
|
-
raise DeploymentKitError(
|
|
117
|
-
"Public-key SSH verification failed for all resolved addresses: " + "; ".join(failures)
|
|
118
|
-
)
|
|
1
|
+
"""Strict SSH verification primitives for logical targets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import socket
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .errors import DeploymentKitError
|
|
12
|
+
from .security import check_ssh_material_security, validate_target_trust_file
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def required_tool(name: str) -> str:
|
|
16
|
+
path = shutil.which(name)
|
|
17
|
+
if path is None:
|
|
18
|
+
raise DeploymentKitError(f"Required OpenSSH tool is unavailable: {name}")
|
|
19
|
+
return path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resolve_addresses(profile: dict[str, Any]) -> list[str]:
|
|
23
|
+
override = profile["addressOverride"]
|
|
24
|
+
if override:
|
|
25
|
+
return [override]
|
|
26
|
+
try:
|
|
27
|
+
answers = socket.getaddrinfo(
|
|
28
|
+
profile["hostName"],
|
|
29
|
+
profile["port"],
|
|
30
|
+
type=socket.SOCK_STREAM,
|
|
31
|
+
)
|
|
32
|
+
except OSError as error:
|
|
33
|
+
raise DeploymentKitError(
|
|
34
|
+
f"Cannot resolve target hostname {profile['hostName']}: {error}. "
|
|
35
|
+
"Use only a separately verified address override; do not scan the network."
|
|
36
|
+
) from error
|
|
37
|
+
addresses = sorted({answer[4][0] for answer in answers})
|
|
38
|
+
if not addresses:
|
|
39
|
+
raise DeploymentKitError(f"Target hostname {profile['hostName']} returned no addresses.")
|
|
40
|
+
return addresses
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def ssh_args(profile: dict[str, Any], address: str, *, command: str = "true") -> list[str]:
|
|
44
|
+
return [
|
|
45
|
+
required_tool("ssh"),
|
|
46
|
+
"-F",
|
|
47
|
+
"none",
|
|
48
|
+
"-o",
|
|
49
|
+
"BatchMode=yes",
|
|
50
|
+
"-o",
|
|
51
|
+
"ConnectTimeout=8",
|
|
52
|
+
"-o",
|
|
53
|
+
"StrictHostKeyChecking=yes",
|
|
54
|
+
"-o",
|
|
55
|
+
"UpdateHostKeys=no",
|
|
56
|
+
"-o",
|
|
57
|
+
"CheckHostIP=no",
|
|
58
|
+
"-o",
|
|
59
|
+
"VerifyHostKeyDNS=no",
|
|
60
|
+
"-o",
|
|
61
|
+
"GlobalKnownHostsFile=none",
|
|
62
|
+
"-o",
|
|
63
|
+
f"UserKnownHostsFile={profile['knownHostsFile']}",
|
|
64
|
+
"-o",
|
|
65
|
+
f"HostKeyAlias={profile['sshAlias']}",
|
|
66
|
+
"-o",
|
|
67
|
+
f"IdentityFile={profile['identityFile']}",
|
|
68
|
+
"-o",
|
|
69
|
+
"IdentitiesOnly=yes",
|
|
70
|
+
"-o",
|
|
71
|
+
"PasswordAuthentication=no",
|
|
72
|
+
"-o",
|
|
73
|
+
"KbdInteractiveAuthentication=no",
|
|
74
|
+
"-o",
|
|
75
|
+
"PreferredAuthentications=publickey",
|
|
76
|
+
"-o",
|
|
77
|
+
"ForwardAgent=no",
|
|
78
|
+
"-o",
|
|
79
|
+
"ForwardX11=no",
|
|
80
|
+
"-o",
|
|
81
|
+
"ClearAllForwardings=yes",
|
|
82
|
+
"-p",
|
|
83
|
+
str(profile["port"]),
|
|
84
|
+
f"{profile['user']}@{address}",
|
|
85
|
+
command,
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def verify_access(profile: dict[str, Any]) -> dict[str, str]:
|
|
90
|
+
identity = Path(profile["identityFile"])
|
|
91
|
+
known_hosts = Path(profile["knownHostsFile"])
|
|
92
|
+
if not identity.is_file():
|
|
93
|
+
raise DeploymentKitError(f"Dedicated private key is missing: {identity}")
|
|
94
|
+
if not known_hosts.is_file() or known_hosts.stat().st_size == 0:
|
|
95
|
+
raise DeploymentKitError(
|
|
96
|
+
f"Target-specific trust file is missing or empty: {known_hosts}. "
|
|
97
|
+
"Enroll trust only after independently verifying the host fingerprint."
|
|
98
|
+
)
|
|
99
|
+
check_ssh_material_security(identity, known_hosts)
|
|
100
|
+
validate_target_trust_file(profile)
|
|
101
|
+
|
|
102
|
+
failures: list[str] = []
|
|
103
|
+
for address in resolve_addresses(profile):
|
|
104
|
+
result = subprocess.run(
|
|
105
|
+
ssh_args(profile, address),
|
|
106
|
+
check=False,
|
|
107
|
+
capture_output=True,
|
|
108
|
+
text=True,
|
|
109
|
+
encoding="utf-8",
|
|
110
|
+
errors="replace",
|
|
111
|
+
)
|
|
112
|
+
if result.returncode == 0:
|
|
113
|
+
return {"targetName": profile["targetName"], "address": address, "status": "verified"}
|
|
114
|
+
failures.append(f"{address}: exit {result.returncode}")
|
|
115
|
+
|
|
116
|
+
raise DeploymentKitError(
|
|
117
|
+
"Public-key SSH verification failed for all resolved addresses: " + "; ".join(failures)
|
|
118
|
+
)
|