v0.2.0
Rebases pytruenas' generic host machinery onto [hostctl], keeping only the
TrueNAS-specific parts here: the middleware websocket, the api namespace,
login/2FA, subscriptions, and the upload/download side channels. Everything
else — shell quoting, transport selection, the asyncssh lifecycle, path
backends — is now inherited.
Requires hostctl>=0.1.0,<0.2.
Changed
- BREAKING:
TrueNASClient.shellis gone..shellnow means what it means
throughout hostctl — the bound shell object (client.shell.run(...)). The
SSH connection target lives on the configuration asclient.config.ssh, an
SshConfig. The constructor argument is still spelledshell=and still
takes a connection string (shell="ssh://root@nas"). .run()and.path()now select a transport rather than branching.
Which one serves a call is chosen from the available providers, and
.last_selectionrecords what was tried and why — with credentials redacted.
Previously.run()hard-coded a local-vs-SSH branch andTruenasPath
hand-rolled its own SFTP→websocket fallback.- A remote target with no SSH can now run commands over the web shell. The
TrueNAS JSON-RPC API exposes no remote command execution (verified against
26.0.0-BETA.1: of 781 methods onlycore.resize_shelland
user.shell_choicesare shell-adjacent, and the former only resizes an
already-open session) — so such a host previously had norun()at all.
/websocket/shell, the PTY the web UI's Shell page drives, is a real command
channel on the same port. Passexecutor=["ssh"]to require SSH instead. - The scheme/API-path probe moved from construction to first connect.
TrueNASClient("bad-host")now constructs successfully and raises on first
use. Configs are therefore buildable offline, which is whatHostConfig
requires.
Added
pytruenas.host—TrueNASConfig(ahostctl.host.HostConfig) and
TrueNASHost(aPosixHost).HostConfig("truenas+wss://nas")resolves
through hostctl's registry; every connection stringTrueNASClientaccepts
still works, normalized to atruenas+*scheme.TrueNASClientandTrueNASHostare now one class. They were briefly
two objects that forwarded halves of their surface to each other —
client.run()calledclient.host.run()whilehost.apicalled
host.client.api, each holding a reference to the other.TrueNASClientis
an alias forTrueNASHost, so every existing import and call keeps working,
andclient.host/host.clientboth return the object itself.
TrueNASHost("wss://nas")also takes a connection string directly, with the
same options asTrueNASConfig.from_target.pytruenas.providers—TnasWsPathProvider(thefilesystem.*
websocket leg) andlocal_providers(), which returns hostctl's stock local
executor and path providers unchanged. A local target runs plain
subprocessand uses plain local paths; there is nothing TrueNAS-specific to
add, so pytruenas defines no provider class for it.pytruenas.webshell—WebShellExecutorProvider, command execution over
/websocket/shell. Ordered after SSH; declares its limits rather than hiding
them (stdout and stderr are one stream, no piped input, single-line commands
only — pipes and here-strings work, being ordinary shell syntax).executor=/path=onTrueNASConfig— name the providers to use, in
preference order, as a single name or a sequence:executor=["ssh"],
path=["local", "tnasws"],executor=[]for no command channel at all.
Unknown names, andssh/sftpwithout anSshConfig, raise rather than
composing a host that would fail later. Matches hostctl's own
SystemConfig(executor=..., path=...)spelling.Credentials.from_host_credentials()— maps hostctl's already-parsed
credential mapping (including a URI-supplied OTP) onto aCredentials
subclass, with no second round of string parsing.- Inherited from hostctl:
.capabilities(so a host that genuinely cannot
run commands says so up front rather than failing mid-call),
.last_selection,.info(),.spawn(),.connect()/.close(), and
context-manager support. blackin thedevextra, pinned to the 3.9 floor.
Removed
- BREAKING:
pytruenas.jsonrpcis nowpytruenas.connection, and its
Clientclass isTrueNASWSConnection.Clientwas doubly wrong: the class
is not generic JSON-RPC (it knowscore.subscribe, TrueNAS error codes, and
the middleware unix socket), and the name collided withTrueNASClient.
client.connis the connection, with.websocketkept as an alias. - BREAKING:
pytruenas.clientandpytruenas._connare gone.
from pytruenas import TrueNASClientis unaffected._connwas a re-export
shim for swapping the client implementation, which never happened;client
had been reduced to an alias by the host/client merge. - ~190 lines of generic host machinery: shell quoting, the local-vs-SSH branch
inrun(), the asyncssh connection, and_shellquote.
Known limitations
- A local unix-socket client cannot use
download(). The HTTP side channel
resolves tohttps://localhostand trips the appliance's self-signed
certificate. This is pre-existing and unrelated to the migration — the URL
construction is byte-identical to 0.1.1. - The web shell merges stdout and stderr (a PTY is one stream), takes no
pipedinput=, and requires single-line commands. Pipes and here-strings
work, being ordinary shell syntax. It is ordered after SSH for these reasons.
Full Changelog: v0.1.1...v0.2.0