Discovery library for remote log workflows over SSH/SFTP
Project description
ssh-discovery
Python library for discovering remote filesystem entries over SSH/SFTP.
Overview
ssh-discovery connects to a remote host over SSH/SFTP, scans a path for
matching entries, and returns their metadata as Python objects. It can also do
deterministic incremental discovery when the caller provides an anchor tuple.
This package supports:
- immediate directories in a path
- immediate files in a path
- recursive file discovery under a path
- optional incremental discovery using
(anchor_mtime, anchor_path)
Architecture
ssh_discovery/
|- __init__.py Public API re-exports
|- service.py DiscoveryService - main entry point
|- config.py Typed config dataclasses
|- models.py Shared domain models
|- transport/ SSH/SFTP connectivity and remote entry listing
\- common/ Shared errors and helper utilities
Data flow per DiscoveryService.run() call
- Open SSH.
- Open SFTP.
- List matching remote entries.
- Sort results by
(mtime, path). - Optionally drop entries whose
(mtime, path)is not newer than the anchor tuple. - Return
list[RemoteEntry].
Installation
pip install ssh-discovery
Or from source:
git clone <repo>
cd ssh-discovery
pip install -e ".[dev]"
Requirements: Python 3.11+ and Paramiko.
Usage
from ssh_discovery import DiscoveryConfig, DiscoveryService, SshConfig
config = DiscoveryConfig(
ssh=SshConfig(
host="192.168.1.100",
port=22,
username="logsync",
private_key_path="/path/to/id_ed25519",
connect_timeout_seconds=10.0,
keepalive_seconds=30,
),
remote_path="/var/log/mylogs",
file_glob="*.log",
mode="files_recursive",
anchor_mtime=None, # or a timezone-aware datetime anchor
anchor_path=None, # required together with anchor_mtime
)
service = DiscoveryService(config)
remote_entries = service.run()
for entry in remote_entries:
print(entry.path, entry.mtime, entry.mode, entry.is_file, entry.is_dir)
Incremental discovery
If your orchestrator tracks the last processed entry, pass its (mtime, path)
back into the next run:
from datetime import datetime, timezone
config = DiscoveryConfig(
ssh=SshConfig(
host="192.168.1.100",
private_key_path="/path/to/id_ed25519",
),
remote_path="/var/log/mylogs",
mode="files_recursive",
anchor_mtime=datetime(2026, 3, 23, 12, 0, tzinfo=timezone.utc),
anchor_path="/var/log/mylogs/app-2026-03-23.log",
)
Error handling
from ssh_discovery import SshDiscoveryError, TransportError
try:
remote_entries = service.run()
except TransportError as exc:
logger.error("Transport failure: %s", exc)
except SshDiscoveryError as exc:
logger.error("Discovery failure: %s", exc)
Logging
This package uses standard Python module loggers and does not configure handlers
or formatters. Configure logging in your application before calling
service.run().
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
Configuration reference
SshConfig
| Field | Type | Default | Description |
|---|---|---|---|
host |
str |
- | IP or hostname of the remote host |
port |
int |
22 |
SSH port |
username |
str |
"logsync" |
SSH username |
private_key_path |
str | None |
None |
Path to private key file |
password |
str | None |
None |
Password auth or private-key passphrase |
connect_timeout_seconds |
float |
30.0 |
TCP, banner, and auth timeout |
keepalive_seconds |
int |
60 |
SSH keepalive interval, 0 disables |
known_hosts_path |
str | None |
None |
Known-hosts path for strict verification |
allow_unknown_hosts |
bool |
False |
Accept unknown host keys automatically |
At least one of private_key_path or password is required.
Unknown SSH hosts are rejected by default. Set allow_unknown_hosts=True
only in controlled environments where trust-on-first-use is acceptable.
DiscoveryConfig
| Field | Type | Default | Description |
|---|---|---|---|
ssh |
SshConfig |
- | SSH connection settings |
remote_path |
str |
- | Remote path to scan |
file_glob |
str |
"*" |
Glob pattern for matching entry names |
mode |
"directories" | "files" | "files_recursive" |
"directories" |
Discovery behavior |
anchor_mtime |
datetime | None |
None |
Anchor timestamp for incremental discovery |
anchor_path |
str | None |
None |
Anchor path tiebreaker for incremental discovery |
anchor_mtime and anchor_path must be provided together.
Running tests
pytest
pytest --cov=ssh_discovery --cov-report=term-missing
Notes
- Results are sorted by remote
mtime, withpathas a tiebreaker. - When
anchor_mtimeandanchor_pathare set, only entries with(mtime, path)greater than that tuple are returned. - Returned
RemoteEntryobjects includename,path,mtime, and rawmode. - Convenience properties
is_dir,is_file, andis_symlinkare derived frommode. - Persistence and orchestration are intentionally external to the library.
- SSH keys are preferred over passwords for production deployments.
- Unknown SSH host keys are rejected by default unless explicitly allowed.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ssh_discovery-0.1.2.tar.gz.
File metadata
- Download URL: ssh_discovery-0.1.2.tar.gz
- Upload date:
- Size: 12.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f69c3c43ac5067fcd4bd51218345ed8f400540a9d2fb3b1a7e833a270c68c897
|
|
| MD5 |
55b1badd50d963bef8d0c55e52e74b56
|
|
| BLAKE2b-256 |
0a1a5861269a91ed189cd8d52bdd8bd41427d4779956a2fa8c37598f95ff9cab
|
Provenance
The following attestation bundles were made for ssh_discovery-0.1.2.tar.gz:
Publisher:
release.yml on DreamyStranger/ssh-discovery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_discovery-0.1.2.tar.gz -
Subject digest:
f69c3c43ac5067fcd4bd51218345ed8f400540a9d2fb3b1a7e833a270c68c897 - Sigstore transparency entry: 1157910748
- Sigstore integration time:
-
Permalink:
DreamyStranger/ssh-discovery@7575c0a27a471087d6477c24b4e137751109109b -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/DreamyStranger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7575c0a27a471087d6477c24b4e137751109109b -
Trigger Event:
push
-
Statement type:
File details
Details for the file ssh_discovery-0.1.2-py3-none-any.whl.
File metadata
- Download URL: ssh_discovery-0.1.2-py3-none-any.whl
- Upload date:
- Size: 12.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2231509ba416c12d30542dd7a7b11482ad311ef51f1b3dadb4aa561d4d1d8aa2
|
|
| MD5 |
1c81e01d96964736da91ecbd5b1ed25d
|
|
| BLAKE2b-256 |
cd0f01380ab080bda941f032959665b648fdf031f760a2fdc18086f9e2465986
|
Provenance
The following attestation bundles were made for ssh_discovery-0.1.2-py3-none-any.whl:
Publisher:
release.yml on DreamyStranger/ssh-discovery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ssh_discovery-0.1.2-py3-none-any.whl -
Subject digest:
2231509ba416c12d30542dd7a7b11482ad311ef51f1b3dadb4aa561d4d1d8aa2 - Sigstore transparency entry: 1157910786
- Sigstore integration time:
-
Permalink:
DreamyStranger/ssh-discovery@7575c0a27a471087d6477c24b4e137751109109b -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/DreamyStranger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7575c0a27a471087d6477c24b4e137751109109b -
Trigger Event:
push
-
Statement type: