sshscript 4.0.2__tar.gz → 5.0.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.
- sshscript-5.0.0/AI_GUIDE.md +58 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/API_GUIDE.md +144 -11
- {sshscript-4.0.2 → sshscript-5.0.0}/CHANGELOG.md +12 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/MANIFEST.in +3 -0
- {sshscript-4.0.2/src/sshscript.egg-info → sshscript-5.0.0}/PKG-INFO +64 -8
- {sshscript-4.0.2 → sshscript-5.0.0}/README.md +63 -7
- {sshscript-4.0.2 → sshscript-5.0.0}/SECURITY.md +11 -2
- {sshscript-4.0.2 → sshscript-5.0.0}/VERSIONING.md +18 -1
- sshscript-5.0.0/skills/sshscript/SKILL.md +76 -0
- sshscript-5.0.0/skills/sshscript/references/agent-guide.md +360 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/_version.py +1 -1
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/session.py +100 -2
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/session.pyi +15 -3
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sessionsettings.py +17 -1
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_command_job.py +5 -1
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_logger_integration.py +16 -3
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_openssh_integration.py +30 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_recommended_examples.py +22 -1
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_ssh_config.py +7 -1
- sshscript-5.0.0/src/sshscript/unittest/test_ssh_security.py +531 -0
- {sshscript-4.0.2 → sshscript-5.0.0/src/sshscript.egg-info}/PKG-INFO +64 -8
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/SOURCES.txt +4 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/check_public_types.py +14 -5
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/prepare_release.py +3 -0
- sshscript-5.0.0/tools/sync_ai_docs.py +97 -0
- sshscript-4.0.2/src/sshscript/unittest/test_ssh_security.py +0 -295
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/dependabot.yml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/pull_request_template.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/workflows/ci.yml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/workflows/codeql.yml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.github/workflows/release.yml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/.gitignore +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/CODE_OF_CONDUCT.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/CONTRIBUTING.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/EXCEPTIONS.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/LICENSE.txt +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/RELEASING.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/SUPPORT.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/pyproject.toml +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/setup.cfg +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/_sshscript_cli.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/__init__.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/__init__.pyi +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/_runtime.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelgeneric.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelssh.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelsubprocess.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelutils.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandjob.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandjob.pyi +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandresult.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandresult.pyi +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/consolejob.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/dollar.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/dollarchanger.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/dollarparser.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/errorutils.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/patching.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/py.typed +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sessionwrapper.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sessionwrapper.pyi +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/spyimporter.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sshconfig.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sshscript.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/stdio.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/tokenparser.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.console-authentication.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.console-results.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.dollar-syntax-tests.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.module-tests.md +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/check_package_asserts.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/dollar_syntax.spy +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/dollar_syntax_fixture.spy +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/language_fixture.spy +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_channelgeneric_expect.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_check_file.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_command_api.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_console_authentication.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_console_job.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_console_results.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_dollar_command_escapes.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_file_transfer.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_logger_api.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_native_auth_runner.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_native_console_authentication.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_production_contract.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_public_types.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_python_version.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_release_inputs.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_session_close_reporting.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_session_proxy_cleanup.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_session_settings.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_spy_source_mapping.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_spy_thread_session.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_sshscript_dollar_syntax.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_sshscript_module.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_stdin_contract.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_stdio_dynamic_string.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_syntax_error.spy +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_update_check.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_version_policy.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/dependency_links.txt +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/entry_points.txt +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/requires.txt +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/top_level.txt +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/check_baseline_examples.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/check_release.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/publish_release.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/run_checks.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/run_native_auth_ci.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/setup_openssh_ci.sh +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/sync_api_guide.py +0 -0
- {sshscript-4.0.2 → sshscript-5.0.0}/tools/version_policy.py +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# SSHScript for AI agents
|
|
2
|
+
|
|
3
|
+
**Documentation contract: SSHScript 5.0.0; Python 3.11+.**
|
|
4
|
+
|
|
5
|
+
Start with the [agent guide](skills/sshscript/references/agent-guide.md).
|
|
6
|
+
It explains version checks, API selection, command results, input, deadlines,
|
|
7
|
+
authentication and cleanup. Read only the task-relevant sections of the
|
|
8
|
+
[canonical API guide](API_GUIDE.md) and [version policy](VERSIONING.md).
|
|
9
|
+
|
|
10
|
+
The [SSHScript skill](skills/sshscript/SKILL.md) supplies a compact operating
|
|
11
|
+
workflow. Its folder contains the supporting guide; copy the entire
|
|
12
|
+
`skills/sshscript/` folder when installing it in an agent's skill directory.
|
|
13
|
+
It teaches SSHScript usage, not development of SSHScript itself or a particular
|
|
14
|
+
recipe library. Agents without skill support can read these Markdown files.
|
|
15
|
+
|
|
16
|
+
## Share one URL
|
|
17
|
+
|
|
18
|
+
The website entry is [SSHScript for AI Agents](https://iapyeh.github.io/sshscript/v3a/ai-agents/).
|
|
19
|
+
After these files are published, users can give that URL and a task to an agent:
|
|
20
|
+
|
|
21
|
+
> Read the SSHScript AI agent entry at
|
|
22
|
+
> https://iapyeh.github.io/sshscript/v3a/ai-agents/ and follow its reading order.
|
|
23
|
+
> Check the SSHScript version in my environment before selecting APIs. Help me
|
|
24
|
+
> complete this task: [describe the task, target hosts and expected output].
|
|
25
|
+
> If you cannot read a required document, report that limitation rather than
|
|
26
|
+
> guessing its contents. Explain the proposed commands before execution and
|
|
27
|
+
> stay within the operations I authorize.
|
|
28
|
+
|
|
29
|
+
A URL supplies reference material for the current task. It does not install
|
|
30
|
+
SSHScript, install a skill, grant execution permission, or guarantee persistent
|
|
31
|
+
memory. An agent needs URL-reading tools, or the user must provide the files.
|
|
32
|
+
|
|
33
|
+
## Maintain the source and website
|
|
34
|
+
|
|
35
|
+
Maintain this entry, `skills/sshscript/SKILL.md`, and its reference guide in
|
|
36
|
+
the designated `working_branch` checkout on branch `working_branch`.
|
|
37
|
+
The landing page is maintained in `gh-pages/v3a/ai-agents/index.md` on branch
|
|
38
|
+
`gh-pages`. The API guide remains authoritative for public behavior.
|
|
39
|
+
|
|
40
|
+
From `working_branch`, synchronize the generated website guide, plain-text
|
|
41
|
+
files and portable skill ZIP with:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
python3 tools/sync_ai_docs.py ../gh-pages/v3a
|
|
45
|
+
python3 tools/sync_ai_docs.py ../gh-pages/v3a --check
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The ZIP includes the skill and its reference guide, plus snapshots of
|
|
49
|
+
`API_GUIDE.md` and `VERSIONING.md` for offline use. These snapshots are generated
|
|
50
|
+
from the source documents; do not edit the ZIP or generated website copies.
|
|
51
|
+
The sync command checks the destination branch before writing. Website pages
|
|
52
|
+
keep their Last Updated timestamp aligned with their mtime.
|
|
53
|
+
|
|
54
|
+
When the execution contract changes, review the skill and guide, update their
|
|
55
|
+
contract labels and the landing page, then regenerate the website assets.
|
|
56
|
+
Validate links, skill structure, example behavior, source/ZIP equality and
|
|
57
|
+
the rendered website. Source and website publishing are separate operations;
|
|
58
|
+
copying files does not publish either of them.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Recommended SSHScript API
|
|
2
2
|
|
|
3
3
|
This is the canonical entry point for new code and AI-generated examples.
|
|
4
|
-
**Current contract:
|
|
5
|
-
SSHScript 4.0.2; installing `sshscript==3.1.5` does not provide them.
|
|
4
|
+
**Current contract: 5.0.0**, Python 3.11+. Sections marked **4.0** require
|
|
5
|
+
SSHScript 4.0.2 or newer; installing `sshscript==3.1.5` does not provide them.
|
|
6
6
|
Examples labelled 3.1.5 remain verified compatibility examples for that baseline.
|
|
7
7
|
Pin the exact artifact version and record it with execution diagnostics when
|
|
8
8
|
handing off automation. See [version policy and migration](VERSIONING.md).
|
|
@@ -31,7 +31,7 @@ identifiers and handoff reports are future work, not a current API guarantee.
|
|
|
31
31
|
|
|
32
32
|
## One command, one retained result (4.0; compatible with 3.1.5)
|
|
33
33
|
|
|
34
|
-
Install the current release with `python -m pip install "sshscript==
|
|
34
|
+
Install the current release with `python -m pip install "sshscript==5.0.0"`.
|
|
35
35
|
For reproducing only the historical compatibility baseline, explicitly install
|
|
36
36
|
`sshscript==3.1.5`; the 4.0 sections require the current release.
|
|
37
37
|
An argv list is one command, not a batch. Shell-looking argument text stays data.
|
|
@@ -88,6 +88,139 @@ with closing(Session()) as local:
|
|
|
88
88
|
print(result.stdout)
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
### Private-key paths and nested connections
|
|
92
|
+
|
|
93
|
+
`parent.connect(..., pkey_path=path)` loads an RSA private key from the
|
|
94
|
+
**calling parent Session's host**. A local Session reads the file on localhost;
|
|
95
|
+
a Session connected to host1 reads it on host1 through SFTP before connecting
|
|
96
|
+
to host2. `connect()` returns a child Session and leaves the parent on its
|
|
97
|
+
original host. Calling the original local Session again therefore still reads
|
|
98
|
+
the key on localhost.
|
|
99
|
+
|
|
100
|
+
For example, `local.connect("host1", pkey_path="/local/key_rsa")` reads
|
|
101
|
+
`/local/key_rsa` locally and returns `host1`. Then
|
|
102
|
+
`host1.connect("host2", pkey_path="/remote/key_rsa")` reads `/remote/key_rsa`
|
|
103
|
+
on host1. In `.spy`, an inner `with $.connect(...)` uses the outer scope's
|
|
104
|
+
current Session as its parent.
|
|
105
|
+
|
|
106
|
+
`key_filename=` is forwarded to Paramiko and reads from the local Python
|
|
107
|
+
process's filesystem even for nested connections; it supports additional key
|
|
108
|
+
formats. For an encrypted RSA key, use
|
|
109
|
+
`pkey=parent.pkey(path, password=key_passphrase)` to retain the same host-based
|
|
110
|
+
reading rule. Supplying both `pkey_path` and `pkey` raises `ValueError`.
|
|
111
|
+
See the [agent guide's complete nested example](https://iapyeh.github.io/sshscript/v3a/ai-agents/guide/#ssh-connections-and-private-key-paths).
|
|
112
|
+
|
|
113
|
+
<a id="host-key-sources-unreleased"></a>
|
|
114
|
+
|
|
115
|
+
### Host-key sources (5.0)
|
|
116
|
+
|
|
117
|
+
This section requires SSHScript 5.0.0 or newer.
|
|
118
|
+
`known_hosts` selects the host-key trust source independently of `policy`:
|
|
119
|
+
|
|
120
|
+
| Value | Sources, in order |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `"parent"` (new default) | Only the calling Session's host |
|
|
123
|
+
| `"local"` | Only the root localhost Session |
|
|
124
|
+
| `"chain"` | Calling Session, then each ancestor through localhost |
|
|
125
|
+
|
|
126
|
+
For localhost → host1 → host2 → host3, `host2.connect("host3",
|
|
127
|
+
known_hosts="chain")` consults host2, host1, then localhost. The nearest source
|
|
128
|
+
with a record for the resolved target hostname and port is authoritative.
|
|
129
|
+
A matching key is accepted; a changed key is rejected even if a more distant
|
|
130
|
+
source matches. Only absence of a target record permits continuing the chain.
|
|
131
|
+
Different key algorithms do not permit falling back to another source.
|
|
132
|
+
|
|
133
|
+
Each source reads its own `known_hosts_path`, or `~/.ssh/known_hosts` if unset.
|
|
134
|
+
Remote files are read over SFTP as that Session's original SSH login account;
|
|
135
|
+
SSH handshakes, user authentication and host-key verification still run in the
|
|
136
|
+
local Python process. Neither sudo/su nor a nested connection changes the SFTP
|
|
137
|
+
account. Remote `~/` paths refer to the login account's SFTP home; `~user` paths
|
|
138
|
+
are unsupported. Relative remote paths use the SFTP working directory.
|
|
139
|
+
|
|
140
|
+
```py
|
|
141
|
+
local.set(known_hosts="chain")
|
|
142
|
+
# After connecting to host1 and host2:
|
|
143
|
+
host1.set(known_hosts_path="/home/ops/.ssh/known_hosts")
|
|
144
|
+
host2.set(known_hosts_path="/etc/ssh/ssh_known_hosts")
|
|
145
|
+
with host2.connect("deploy@host3", pkey_path="/home/ops/.ssh/host3_rsa") as host3:
|
|
146
|
+
result = host3.exec_command(["hostname"], check=True, command_timeout=10)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The strategy is inherited by future child Sessions; the path is host-specific
|
|
150
|
+
and is **not inherited**. `known_hosts_path=None` restores the standard path.
|
|
151
|
+
A `connect(known_hosts=...)` override affects only that connection; it does not
|
|
152
|
+
change the parent setting or the child's inherited strategy. `set()`, `get()`
|
|
153
|
+
and matching properties expose both settings. `known_hosts_path` accepts text
|
|
154
|
+
paths and `PathLike[str]`; it is a Session setting, not a `connect()` keyword.
|
|
155
|
+
|
|
156
|
+
`pkey_path` always reads only from the calling Session's host, including with
|
|
157
|
+
`known_hosts="chain"`; private keys are never searched through ancestors.
|
|
158
|
+
|
|
159
|
+
An absent trust file, permission failure, read failure, invalid UTF-8 or invalid
|
|
160
|
+
entry raises an error; no distant source or permissive policy bypasses it.
|
|
161
|
+
Empty files, comments, plain host-key entries (including comma-separated names)
|
|
162
|
+
and hashed hostnames are supported. OpenSSH markers such as `@cert-authority`
|
|
163
|
+
and `@revoked`, wildcard/negated host patterns and unsupported key types raise
|
|
164
|
+
an error rather than being silently ignored. A source file is fully parsed
|
|
165
|
+
before its records are used. Nonstandard ports use `[hostname]:port` records.
|
|
166
|
+
|
|
167
|
+
When every selected source lacks the target, `policy` handles the unknown key.
|
|
168
|
+
`AutoAddPolicy` accepts it into the new client's memory only; these trust files
|
|
169
|
+
are read-only and are not automatically updated locally or remotely.
|
|
170
|
+
|
|
171
|
+
**Migration from 4.0.2:** earlier releases always load localhost host keys. The new default
|
|
172
|
+
`"parent"` makes nested host-key sources consistent with `pkey_path`. To retain
|
|
173
|
+
the previous behavior, set `local.set(known_hosts="local")` before connecting.
|
|
174
|
+
All strategies require a readable, valid file, including the default localhost
|
|
175
|
+
path. Unlike 4.0.2, a missing local file is an error; create an empty
|
|
176
|
+
file if the configured missing-key policy should handle unknown hosts.
|
|
177
|
+
|
|
178
|
+
### Private keys readable only in a privileged console
|
|
179
|
+
|
|
180
|
+
Remote `pkey()` and `pkey_path` use SFTP with the original SSH login account.
|
|
181
|
+
Entering `sudo()` or `su()` does not change that identity, so even
|
|
182
|
+
`$.pkey("/root/.ssh/id_rsa")` inside a root console can fail with a permission
|
|
183
|
+
error. When authorized, read the file with a console command and construct
|
|
184
|
+
the Paramiko key from its captured text:
|
|
185
|
+
|
|
186
|
+
This manual example requires a real privileged console and SSH endpoints;
|
|
187
|
+
it is excluded from credential-free executable examples.
|
|
188
|
+
|
|
189
|
+
```py
|
|
190
|
+
from contextlib import closing
|
|
191
|
+
from io import StringIO
|
|
192
|
+
import paramiko
|
|
193
|
+
from sshscript import Session
|
|
194
|
+
|
|
195
|
+
with closing(Session()) as local:
|
|
196
|
+
with local.connect("ops@host1.example.net", timeout=10,
|
|
197
|
+
banner_timeout=10, auth_timeout=10) as host1:
|
|
198
|
+
with host1.sudo() as root:
|
|
199
|
+
key_result = root("cat /root/.ssh/id_rsa", check=True, command_timeout=10)
|
|
200
|
+
key = paramiko.RSAKey.from_private_key(StringIO(key_result.stdout))
|
|
201
|
+
with host1.connect("deploy@host2.example.net", pkey=key, timeout=10,
|
|
202
|
+
banner_timeout=10, auth_timeout=10) as host2:
|
|
203
|
+
result = host2.exec_command(["hostname"], check=True, command_timeout=10)
|
|
204
|
+
print(result.stdout)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The example requires authorized sudo/SSH access and verified host keys.
|
|
208
|
+
Configure output logging/display to keep private-key text out of logs before
|
|
209
|
+
running the read; do not print or report `key_result` or its stdout. Key text
|
|
210
|
+
also remains in console buffers. Use the matching Paramiko key class for
|
|
211
|
+
other formats; encrypted RSA keys need `password=` on `from_private_key()`.
|
|
212
|
+
An authorized `su()` console can supply the text in the same way.
|
|
213
|
+
|
|
214
|
+
The console returned by `shell()`/`sudo()`/`su()`/`enter()` has no `connect()`
|
|
215
|
+
method, so `$.connect()` is unavailable while a console is current in `.spy`.
|
|
216
|
+
A retained Python Session can still call `connect()` during an active console;
|
|
217
|
+
the connection uses its original SSH transport and does not inherit console
|
|
218
|
+
privilege. The example leaves the console first and calls the retained
|
|
219
|
+
`host1.connect(pkey=key)`, which needs no SFTP key read. See the
|
|
220
|
+
[agent guide's `.spy` equivalent](https://iapyeh.github.io/sshscript/v3a/ai-agents/guide/#keys-readable-only-after-sudosu).
|
|
221
|
+
|
|
222
|
+
### Optional dollar syntax
|
|
223
|
+
|
|
91
224
|
Save dollar syntax as `.spy`, run with `sshscript file.spy`, and syntax-check
|
|
92
225
|
without execution using `sshscript --check file.spy`.
|
|
93
226
|
|
|
@@ -101,7 +234,7 @@ print(result.stdout)
|
|
|
101
234
|
|
|
102
235
|
## 4.0: settings and managed jobs
|
|
103
236
|
|
|
104
|
-
Use SSHScript
|
|
237
|
+
Use SSHScript 5.0.0 for the following examples. CLI `-v`, `--stderr`, and
|
|
105
238
|
`--debug` override root defaults only for that execution. Child Sessions copy
|
|
106
239
|
their parent's settings. `set()` validates before changing anything; `get()`
|
|
107
240
|
returns a copy. Explicit command options override Session policy.
|
|
@@ -120,7 +253,7 @@ the settings dictionary at creation; the policy object itself is shared, so
|
|
|
120
253
|
custom policies with mutable state must account for reuse. Changing the setting
|
|
121
254
|
does not reconfigure existing connections.
|
|
122
255
|
|
|
123
|
-
<!-- example: {"id":"settings", "profile":"
|
|
256
|
+
<!-- example: {"id":"settings", "profile":"5.0.0", "stdout":"True\n1\n"} -->
|
|
124
257
|
```python
|
|
125
258
|
from contextlib import closing
|
|
126
259
|
import paramiko
|
|
@@ -142,7 +275,7 @@ semantics; do not combine it with `command_timeout`.
|
|
|
142
275
|
For an indefinite command such as tcpdump, use `start(timeout=None)`. This safe
|
|
143
276
|
local example uses a small Python process instead of capturing network traffic:
|
|
144
277
|
|
|
145
|
-
<!-- example: {"id":"stop", "profile":"
|
|
278
|
+
<!-- example: {"id":"stop", "profile":"5.0.0", "stdout":"cancelled confirmed\n"} -->
|
|
146
279
|
```python
|
|
147
280
|
from contextlib import closing
|
|
148
281
|
import sys
|
|
@@ -181,7 +314,7 @@ legacy SSH behavior which appended a newline to strings: if a consumer
|
|
|
181
314
|
needs a line, migrate `input=password` to `input=password + "\n"` explicitly.
|
|
182
315
|
Published 3.1.5 still has the legacy SSH behavior.
|
|
183
316
|
|
|
184
|
-
<!-- example: {"id":"exact-stdin", "profile":"
|
|
317
|
+
<!-- example: {"id":"exact-stdin", "profile":"5.0.0", "stdout":"'abc'\n'abc\\n'\n"} -->
|
|
185
318
|
```python
|
|
186
319
|
from contextlib import closing
|
|
187
320
|
import sys
|
|
@@ -250,7 +383,7 @@ or unpack exactly `stdout, stderr, exitcode`. This changes the released 3.1.5
|
|
|
250
383
|
console contract: old `stdout, stderr = shell(command)` must be migrated.
|
|
251
384
|
Shell calls accept command strings; argv execution belongs to Session.
|
|
252
385
|
|
|
253
|
-
<!-- example: {"id":"shell-result", "profile":"
|
|
386
|
+
<!-- example: {"id":"shell-result", "profile":"5.0.0", "stdout":"first 0\n"} -->
|
|
254
387
|
```python
|
|
255
388
|
from contextlib import closing
|
|
256
389
|
from sshscript import Session
|
|
@@ -306,7 +439,7 @@ matching: `iter_stdout()` yields chunks, not packets or lines. Put `stop()` in
|
|
|
306
439
|
`finally`, so condition matches, exceptions, and KeyboardInterrupt all trigger
|
|
307
440
|
cleanup. This credential-free example uses a continuous Python process:
|
|
308
441
|
|
|
309
|
-
<!-- example: {"id":"stream-condition", "profile":"
|
|
442
|
+
<!-- example: {"id":"stream-condition", "profile":"5.0.0", "stdout":"cancelled confirmed\n"} -->
|
|
310
443
|
```python
|
|
311
444
|
from contextlib import closing
|
|
312
445
|
import sys
|
|
@@ -371,7 +504,7 @@ the current Bash console's channel, cwd, environment and identity. It does not
|
|
|
371
504
|
launch through the underlying Session. Ordinary `console(command)` / `$command`
|
|
372
505
|
still waits and returns `CommandResult`; no migration is needed for finite commands.
|
|
373
506
|
|
|
374
|
-
<!-- example: {"id":"console-stream", "profile":"
|
|
507
|
+
<!-- example: {"id":"console-stream", "profile":"5.0.0", "stdout":"cancelled confirmed\nconsole-alive\n"} -->
|
|
375
508
|
```python
|
|
376
509
|
from contextlib import closing
|
|
377
510
|
import shlex
|
|
@@ -511,7 +644,7 @@ remains available for other authentication prompt formats.
|
|
|
511
644
|
These transcripts require a real local account and its authentication policy;
|
|
512
645
|
they are manual examples, separate from the credential-free CI examples above.
|
|
513
646
|
Read passwords with `getpass` rather than storing them in the script. The new
|
|
514
|
-
behavior requires 4.0.2 and does not apply to the installed 3.1.5 release.
|
|
647
|
+
behavior requires 4.0.2 or newer and does not apply to the installed 3.1.5 release.
|
|
515
648
|
|
|
516
649
|
Open a root console through sudo and allow up to 15 seconds for entry:
|
|
517
650
|
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
This project records user-visible changes here. Release artifacts and their
|
|
4
4
|
provenance are available from the linked GitHub Release and PyPI pages.
|
|
5
5
|
|
|
6
|
+
## 5.0.0 — 2026-10-10
|
|
7
|
+
|
|
8
|
+
- Add `known_hosts="parent"` (new default), `"local"` and `"chain"` strategies.
|
|
9
|
+
Nested connections now use the calling host's trust file by default, matching
|
|
10
|
+
`pkey_path` ownership. Set `known_hosts="local"` for previous behavior.
|
|
11
|
+
- Add per-Session `known_hosts_path` without path inheritance. Chain lookup uses
|
|
12
|
+
the nearest target record; changed keys and read/parse errors never fall back.
|
|
13
|
+
Authentication and verification still run locally; trust files are read-only.
|
|
14
|
+
- Isolate connection-timeout tests from runner HOME and verify client cleanup.
|
|
15
|
+
- Document host-key source selection, strict file errors and migration in the
|
|
16
|
+
API, website and portable AI agent skill.
|
|
17
|
+
|
|
6
18
|
## 4.0.2 — 2026-10-09
|
|
7
19
|
|
|
8
20
|
- Align the README, current installation commands, website title, navigation,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
include README.md LICENSE.txt RELEASING.md
|
|
2
|
+
include AI_GUIDE.md
|
|
3
|
+
include skills/sshscript/SKILL.md
|
|
4
|
+
include skills/sshscript/references/agent-guide.md
|
|
2
5
|
include CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md EXCEPTIONS.md SECURITY.md SUPPORT.md
|
|
3
6
|
recursive-include tools *.py
|
|
4
7
|
include tools/setup_openssh_ci.sh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sshscript
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Python automation for local processes, SSH, and SSHScript .spy files
|
|
5
5
|
Author-email: "Yeh, Hsin-Yuan" <iapyeh@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -37,9 +37,12 @@ Dynamic: license-file
|
|
|
37
37
|
[](https://github.com/iapyeh/sshscript/actions/workflows/codeql.yml)
|
|
38
38
|
[](https://github.com/iapyeh/sshscript/blob/release/LICENSE.txt)
|
|
39
39
|
|
|
40
|
+
**AI agent ready automation for local and remote commands.**
|
|
41
|
+
|
|
40
42
|
SSHScript is a Python automation library for running commands locally and over
|
|
41
|
-
SSH through one `Session` API. It
|
|
42
|
-
|
|
43
|
+
SSH through one `Session` API. It provides an agent guide, a portable skill
|
|
44
|
+
bundle, and copyable prompts to help your AI agent use its execution contract.
|
|
45
|
+
Optional dollar syntax supports compact `.spy` automation files.
|
|
43
46
|
|
|
44
47
|
SSHScript aims to help engineers and AI agents execute, understand, and hand
|
|
45
48
|
off automation reliably: identify the execution host, retain each command's
|
|
@@ -47,7 +50,7 @@ output and status, distinguish failure from timeout, and make cleanup and
|
|
|
47
50
|
unknown termination explicit. A successful command exit is evidence about
|
|
48
51
|
that command; verify the deployment or other application outcome separately.
|
|
49
52
|
|
|
50
|
-
**Current release:** [
|
|
53
|
+
**Current release:** [5.0.0](https://github.com/iapyeh/sshscript/releases/tag/v5.0.0)
|
|
51
54
|
(Production/Stable) · **Python:** 3.11 or newer · **Tested:** Python
|
|
52
55
|
3.11–3.14 on Linux and macOS
|
|
53
56
|
|
|
@@ -57,11 +60,45 @@ that command; verify the deployment or other application outcome separately.
|
|
|
57
60
|
[Security](https://github.com/iapyeh/sshscript/security/policy) ·
|
|
58
61
|
[Support](https://github.com/iapyeh/sshscript/blob/release/SUPPORT.md)
|
|
59
62
|
|
|
60
|
-
**Version
|
|
61
|
-
|
|
63
|
+
**Version 5.0.0** preserves the 4.0 command execution contract and changes the
|
|
64
|
+
default host-key source to the calling host. Provision its known_hosts file or
|
|
65
|
+
select `known_hosts="local"` when migrating existing nested connections.
|
|
62
66
|
Upgrade 3.1.5 automation using the migration guide and application regressions.
|
|
63
67
|
See [version policy and migration](VERSIONING.md) before upgrading automation.
|
|
64
68
|
|
|
69
|
+
## AI agent ready
|
|
70
|
+
|
|
71
|
+
**Give your agent one URL:**
|
|
72
|
+
[SSHScript for AI Agents](https://iapyeh.github.io/sshscript/v3a/ai-agents/).
|
|
73
|
+
Prepare it for future automation or give it a task to complete now.
|
|
74
|
+
|
|
75
|
+
- **A guide for agents:** version-aware API selection, exact input handling,
|
|
76
|
+
retained command results, timeouts, authentication and cleanup.
|
|
77
|
+
- **A portable skill bundle:** `SKILL.md` with the agent guide and API and
|
|
78
|
+
version-policy references, ready to install through your agent's skill
|
|
79
|
+
mechanism.
|
|
80
|
+
- **Prompts for humans:** copy a setup prompt to request skill installation and
|
|
81
|
+
a saved tool preference, or a task prompt to describe hosts, inputs and
|
|
82
|
+
expected results.
|
|
83
|
+
|
|
84
|
+
Start with this prompt, even before you have a task:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
Read https://iapyeh.github.io/sshscript/v3a/ai-agents/ and the Agent Guide.
|
|
88
|
+
If you support skills, download and install the complete skill bundle. If you
|
|
89
|
+
support persistent preferences, save this preference: for future local or
|
|
90
|
+
remote command execution and automation, first assess whether SSHScript is
|
|
91
|
+
suitable and use it when appropriate. Report whether the skill can be loaded
|
|
92
|
+
and the preference was saved, and explain any limits.
|
|
93
|
+
For now, do not connect to remote hosts or run demonstration commands.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The guides are ordinary Markdown and web pages for agents that can read files
|
|
97
|
+
or URLs. Skill installation and persistent preferences depend on the agent's
|
|
98
|
+
capabilities. See the [repository AI entry](AI_GUIDE.md),
|
|
99
|
+
[agent guide](skills/sshscript/references/agent-guide.md) and
|
|
100
|
+
[skill instructions](skills/sshscript/SKILL.md) for the source documents.
|
|
101
|
+
|
|
65
102
|
## Recommended API
|
|
66
103
|
|
|
67
104
|
Start with [the canonical API guide](API_GUIDE.md): version-labelled examples,
|
|
@@ -83,6 +120,7 @@ and [the cross-system test guide](src/sshscript/unittest/README.console-authenti
|
|
|
83
120
|
|
|
84
121
|
## Why SSHScript?
|
|
85
122
|
|
|
123
|
+
- Prepare your AI agent with a dedicated guide, portable skill and setup prompts.
|
|
86
124
|
- Use the same interface for local subprocesses and remote SSH commands.
|
|
87
125
|
- Traverse nested SSH connections without rebuilding connection logic.
|
|
88
126
|
- Keep ordinary Python functions, packages, exceptions, data processing, and
|
|
@@ -115,7 +153,7 @@ pin SSHScript and all transitive dependencies in your application's lock file.
|
|
|
115
153
|
To install this release explicitly:
|
|
116
154
|
|
|
117
155
|
```sh
|
|
118
|
-
python3 -m pip install "sshscript==
|
|
156
|
+
python3 -m pip install "sshscript==5.0.0"
|
|
119
157
|
```
|
|
120
158
|
|
|
121
159
|
Use `python3 -m pip install --upgrade sshscript` to upgrade. The optional
|
|
@@ -124,7 +162,7 @@ it never installs an update by itself.
|
|
|
124
162
|
|
|
125
163
|
## 60-second local quickstart
|
|
126
164
|
|
|
127
|
-
This example runs on
|
|
165
|
+
This example runs on 5.0.0 and retains compatibility with the 3.1.5 command API.
|
|
128
166
|
The metadata below records that older compatibility baseline.
|
|
129
167
|
|
|
130
168
|
<!-- example: {"id":"readme-quickstart", "profile":"3.1.5", "stdout":"sshscript is ready\nexit code: 0\n"} -->
|
|
@@ -380,6 +418,24 @@ verified the key. See the
|
|
|
380
418
|
nested connections, timeouts, file transfer, `sudo`, `su`, and interactive
|
|
381
419
|
programs.
|
|
382
420
|
|
|
421
|
+
## Host-key sources in 5.0
|
|
422
|
+
|
|
423
|
+
SSHScript 5.0.0 uses `known_hosts="parent"` as the default: a local
|
|
424
|
+
Session reads localhost's trust file; `host1.connect("host2")` reads host1's
|
|
425
|
+
trust file over SFTP. This matches the host ownership of `pkey_path`.
|
|
426
|
+
SSHScript 4.0.2 and earlier use localhost for all host-key checks.
|
|
427
|
+
|
|
428
|
+
Set `session.set(known_hosts="chain")` to search from the calling Session
|
|
429
|
+
through its ancestors to localhost. The nearest layer with a target record
|
|
430
|
+
wins; a mismatch or read/parse error stops the connection. Set
|
|
431
|
+
`session.set(known_hosts="local")` to preserve previous behavior.
|
|
432
|
+
Each Session can set its own `known_hosts_path`; this path is not inherited.
|
|
433
|
+
`connect(known_hosts=...)` overrides only one connection. `pkey_path` remains
|
|
434
|
+
on the calling host for every strategy. `policy` handles only unknown keys;
|
|
435
|
+
`AutoAddPolicy` does not write these trust files. See the
|
|
436
|
+
[host-key source contract](API_GUIDE.md#host-key-sources-unreleased) for supported
|
|
437
|
+
file formats, strict error handling and migration.
|
|
438
|
+
|
|
383
439
|
## Reusing SSH configuration
|
|
384
440
|
|
|
385
441
|
Connections from a local session read `~/.ssh/config` if it exists. Supported
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
[](https://github.com/iapyeh/sshscript/actions/workflows/codeql.yml)
|
|
7
7
|
[](https://github.com/iapyeh/sshscript/blob/release/LICENSE.txt)
|
|
8
8
|
|
|
9
|
+
**AI agent ready automation for local and remote commands.**
|
|
10
|
+
|
|
9
11
|
SSHScript is a Python automation library for running commands locally and over
|
|
10
|
-
SSH through one `Session` API. It
|
|
11
|
-
|
|
12
|
+
SSH through one `Session` API. It provides an agent guide, a portable skill
|
|
13
|
+
bundle, and copyable prompts to help your AI agent use its execution contract.
|
|
14
|
+
Optional dollar syntax supports compact `.spy` automation files.
|
|
12
15
|
|
|
13
16
|
SSHScript aims to help engineers and AI agents execute, understand, and hand
|
|
14
17
|
off automation reliably: identify the execution host, retain each command's
|
|
@@ -16,7 +19,7 @@ output and status, distinguish failure from timeout, and make cleanup and
|
|
|
16
19
|
unknown termination explicit. A successful command exit is evidence about
|
|
17
20
|
that command; verify the deployment or other application outcome separately.
|
|
18
21
|
|
|
19
|
-
**Current release:** [
|
|
22
|
+
**Current release:** [5.0.0](https://github.com/iapyeh/sshscript/releases/tag/v5.0.0)
|
|
20
23
|
(Production/Stable) · **Python:** 3.11 or newer · **Tested:** Python
|
|
21
24
|
3.11–3.14 on Linux and macOS
|
|
22
25
|
|
|
@@ -26,11 +29,45 @@ that command; verify the deployment or other application outcome separately.
|
|
|
26
29
|
[Security](https://github.com/iapyeh/sshscript/security/policy) ·
|
|
27
30
|
[Support](https://github.com/iapyeh/sshscript/blob/release/SUPPORT.md)
|
|
28
31
|
|
|
29
|
-
**Version
|
|
30
|
-
|
|
32
|
+
**Version 5.0.0** preserves the 4.0 command execution contract and changes the
|
|
33
|
+
default host-key source to the calling host. Provision its known_hosts file or
|
|
34
|
+
select `known_hosts="local"` when migrating existing nested connections.
|
|
31
35
|
Upgrade 3.1.5 automation using the migration guide and application regressions.
|
|
32
36
|
See [version policy and migration](VERSIONING.md) before upgrading automation.
|
|
33
37
|
|
|
38
|
+
## AI agent ready
|
|
39
|
+
|
|
40
|
+
**Give your agent one URL:**
|
|
41
|
+
[SSHScript for AI Agents](https://iapyeh.github.io/sshscript/v3a/ai-agents/).
|
|
42
|
+
Prepare it for future automation or give it a task to complete now.
|
|
43
|
+
|
|
44
|
+
- **A guide for agents:** version-aware API selection, exact input handling,
|
|
45
|
+
retained command results, timeouts, authentication and cleanup.
|
|
46
|
+
- **A portable skill bundle:** `SKILL.md` with the agent guide and API and
|
|
47
|
+
version-policy references, ready to install through your agent's skill
|
|
48
|
+
mechanism.
|
|
49
|
+
- **Prompts for humans:** copy a setup prompt to request skill installation and
|
|
50
|
+
a saved tool preference, or a task prompt to describe hosts, inputs and
|
|
51
|
+
expected results.
|
|
52
|
+
|
|
53
|
+
Start with this prompt, even before you have a task:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
Read https://iapyeh.github.io/sshscript/v3a/ai-agents/ and the Agent Guide.
|
|
57
|
+
If you support skills, download and install the complete skill bundle. If you
|
|
58
|
+
support persistent preferences, save this preference: for future local or
|
|
59
|
+
remote command execution and automation, first assess whether SSHScript is
|
|
60
|
+
suitable and use it when appropriate. Report whether the skill can be loaded
|
|
61
|
+
and the preference was saved, and explain any limits.
|
|
62
|
+
For now, do not connect to remote hosts or run demonstration commands.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The guides are ordinary Markdown and web pages for agents that can read files
|
|
66
|
+
or URLs. Skill installation and persistent preferences depend on the agent's
|
|
67
|
+
capabilities. See the [repository AI entry](AI_GUIDE.md),
|
|
68
|
+
[agent guide](skills/sshscript/references/agent-guide.md) and
|
|
69
|
+
[skill instructions](skills/sshscript/SKILL.md) for the source documents.
|
|
70
|
+
|
|
34
71
|
## Recommended API
|
|
35
72
|
|
|
36
73
|
Start with [the canonical API guide](API_GUIDE.md): version-labelled examples,
|
|
@@ -52,6 +89,7 @@ and [the cross-system test guide](src/sshscript/unittest/README.console-authenti
|
|
|
52
89
|
|
|
53
90
|
## Why SSHScript?
|
|
54
91
|
|
|
92
|
+
- Prepare your AI agent with a dedicated guide, portable skill and setup prompts.
|
|
55
93
|
- Use the same interface for local subprocesses and remote SSH commands.
|
|
56
94
|
- Traverse nested SSH connections without rebuilding connection logic.
|
|
57
95
|
- Keep ordinary Python functions, packages, exceptions, data processing, and
|
|
@@ -84,7 +122,7 @@ pin SSHScript and all transitive dependencies in your application's lock file.
|
|
|
84
122
|
To install this release explicitly:
|
|
85
123
|
|
|
86
124
|
```sh
|
|
87
|
-
python3 -m pip install "sshscript==
|
|
125
|
+
python3 -m pip install "sshscript==5.0.0"
|
|
88
126
|
```
|
|
89
127
|
|
|
90
128
|
Use `python3 -m pip install --upgrade sshscript` to upgrade. The optional
|
|
@@ -93,7 +131,7 @@ it never installs an update by itself.
|
|
|
93
131
|
|
|
94
132
|
## 60-second local quickstart
|
|
95
133
|
|
|
96
|
-
This example runs on
|
|
134
|
+
This example runs on 5.0.0 and retains compatibility with the 3.1.5 command API.
|
|
97
135
|
The metadata below records that older compatibility baseline.
|
|
98
136
|
|
|
99
137
|
<!-- example: {"id":"readme-quickstart", "profile":"3.1.5", "stdout":"sshscript is ready\nexit code: 0\n"} -->
|
|
@@ -349,6 +387,24 @@ verified the key. See the
|
|
|
349
387
|
nested connections, timeouts, file transfer, `sudo`, `su`, and interactive
|
|
350
388
|
programs.
|
|
351
389
|
|
|
390
|
+
## Host-key sources in 5.0
|
|
391
|
+
|
|
392
|
+
SSHScript 5.0.0 uses `known_hosts="parent"` as the default: a local
|
|
393
|
+
Session reads localhost's trust file; `host1.connect("host2")` reads host1's
|
|
394
|
+
trust file over SFTP. This matches the host ownership of `pkey_path`.
|
|
395
|
+
SSHScript 4.0.2 and earlier use localhost for all host-key checks.
|
|
396
|
+
|
|
397
|
+
Set `session.set(known_hosts="chain")` to search from the calling Session
|
|
398
|
+
through its ancestors to localhost. The nearest layer with a target record
|
|
399
|
+
wins; a mismatch or read/parse error stops the connection. Set
|
|
400
|
+
`session.set(known_hosts="local")` to preserve previous behavior.
|
|
401
|
+
Each Session can set its own `known_hosts_path`; this path is not inherited.
|
|
402
|
+
`connect(known_hosts=...)` overrides only one connection. `pkey_path` remains
|
|
403
|
+
on the calling host for every strategy. `policy` handles only unknown keys;
|
|
404
|
+
`AutoAddPolicy` does not write these trust files. See the
|
|
405
|
+
[host-key source contract](API_GUIDE.md#host-key-sources-unreleased) for supported
|
|
406
|
+
file formats, strict error handling and migration.
|
|
407
|
+
|
|
352
408
|
## Reusing SSH configuration
|
|
353
409
|
|
|
354
410
|
Connections from a local session read `~/.ssh/config` if it exists. Supported
|
|
@@ -65,11 +65,20 @@ termination. Verify application outcomes and reconcile unknown termination.
|
|
|
65
65
|
|
|
66
66
|
### SSH identity and host keys
|
|
67
67
|
|
|
68
|
-
SSH connections
|
|
69
|
-
|
|
68
|
+
SSH connections reject unknown or changed keys by default. SSHScript 4.0.2 and earlier
|
|
69
|
+
load localhost system host keys. SSHScript 5.0.0 defaults to
|
|
70
|
+
`known_hosts="parent"`, reading the calling Session host's trust file. Remote
|
|
71
|
+
trust files are read over SFTP using the original SSH login account; verification
|
|
72
|
+
still runs locally. `"chain"` trusts the nearest source with a target record;
|
|
73
|
+
it never falls back after a mismatch or file error. Trusting a parent's records
|
|
74
|
+
also trusts that host/account to maintain them. Use `"local"` to retain the
|
|
75
|
+
previous source selection. See the [host-key source contract](API_GUIDE.md#host-key-sources-unreleased).
|
|
76
|
+
|
|
77
|
+
The application is responsible for provisioning `known_hosts` and
|
|
70
78
|
verifying a new fingerprint through an independent channel. Supplying an
|
|
71
79
|
automatic-acceptance Paramiko policy is an explicit opt-out from this default
|
|
72
80
|
and is not recommended for production enrollment.
|
|
81
|
+
AutoAddPolicy does not persist keys into these local or remote trust files.
|
|
73
82
|
|
|
74
83
|
SSH authentication ultimately follows Paramiko and server policy. Prefer an
|
|
75
84
|
SSH agent, managed key, or secret manager. Never store passwords or private
|
|
@@ -9,7 +9,8 @@ claim that a command or deployment is safe or successful.
|
|
|
9
9
|
| Version | Contract and status |
|
|
10
10
|
| --- | --- |
|
|
11
11
|
| 3.1.5 | Published baseline described in API_GUIDE.md; one-shot Session commands return CommandResult, but persistent console calls return two live buffers. |
|
|
12
|
-
| 4.0.2 |
|
|
12
|
+
| 4.0.2 | Previous production contract: completed console results, settings, managed jobs and authenticated-console handshakes; incompatible with parts of 3.1.5. |
|
|
13
|
+
| 5.0.0 | Current production contract: calling-host trust by default, explicit local/chain strategies and strict known_hosts file errors; preserves the 4.0 command execution contract. |
|
|
13
14
|
|
|
14
15
|
Version 4.0.1 introduced the published 4.0 contract after release validation.
|
|
15
16
|
Version 4.0.2 is a documentation and link maintenance patch with the same public
|
|
@@ -17,6 +18,22 @@ execution contract. Native-system validation limits remain documented.
|
|
|
17
18
|
Use the normal Python Session API as the canonical entry point; optional .spy
|
|
18
19
|
syntax follows the same versioned execution contract.
|
|
19
20
|
|
|
21
|
+
## 5.0 host-key source migration
|
|
22
|
+
|
|
23
|
+
SSHScript 5.0.0 defaults `known_hosts` to `"parent"`: nested
|
|
24
|
+
connections read the calling Session host's known_hosts through SFTP.
|
|
25
|
+
4.0.2 reads localhost's known_hosts for every connection. Existing nested
|
|
26
|
+
workflows relying on localhost trust records must opt into
|
|
27
|
+
`session.set(known_hosts="local")`, or provision the parent's trust file.
|
|
28
|
+
The new default requires a readable, valid trust file, even for a local
|
|
29
|
+
Session; an empty file allows the configured missing-key policy to decide.
|
|
30
|
+
`"chain"` searches the nearest host first and falls back only when no target
|
|
31
|
+
record exists, never after a mismatch or file error. `pkey_path` ownership is
|
|
32
|
+
unchanged. See [host-key sources](API_GUIDE.md#host-key-sources-unreleased).
|
|
33
|
+
This default and the strict file-error behavior are incompatible with 4.0.2,
|
|
34
|
+
so they are introduced in a new major version. Explicit `known_hosts="local"`
|
|
35
|
+
preserves the previous source location, while still requiring a readable file.
|
|
36
|
+
|
|
20
37
|
## Numbering and compatibility
|
|
21
38
|
|
|
22
39
|
Versions follow PEP 440 with major.minor.patch compatibility semantics:
|