Development and release validation#
Run these commands from ssh-server-manager/. Use the project .venv, not an
existing user installation. On Windows, replace .venv/bin/python with
.venv\Scripts\python.exe. Frontend development needs Node.js 22.12 or newer;
CI pins Node.js 22.22.3. Installed wheels and sdists serve the committed build
and do not require Node.js.
Backend, contract and frontend checks#
.venv/bin/python -m pytest tests/
.venv/bin/python scripts/export-openapi.py --check contracts/api-v1.openapi.json
npm ci --prefix frontend
npm run check-types --prefix frontend
npm run typecheck --prefix frontend
npm test --prefix frontend
npm run check-build --prefix frontend
The OpenAPI check detects a stale committed API contract. check-types
generates TypeScript types in a temporary directory and compares them with
frontend/src/generated.ts; it never updates the source as a side effect.
When changing a contract, update the OpenAPI file and generated types explicitly
before rerunning the checks.
check-build makes a fresh temporary production build and compares every output
with scripts/ssh_server_manager/assets/ui/. It checks the manifest graph,
local asset references, missing/extra files and the absence of source maps.
The initial HTML, entry JavaScript and static dependencies must fit within
150 KiB gzip; all shipped JavaScript/CSS and HTML must fit within
250 KiB gzip. Compression uses level 9, and deferred page chunks count
toward the total. tests/test_ui_assets.py independently validates the shipped
manifest and asset budgets.
After changing frontend source, build the committed assets explicitly:
npm run build --prefix frontend
npm run check-build --prefix frontend -- --allow-uncommitted
The local flag permits uncommitted assets while still requiring byte-for-byte agreement with a fresh build. The normal check requires the assets to have no Git changes; run it again after committing. Do not hand-edit hashed build files.
Browser and real SSH/SFTP acceptance#
Install the test extra and Chromium before running the browser projects:
.venv/bin/python -m pip install -e ".[test]" -c contracts/constraints.txt
npm exec --prefix frontend -- playwright install chromium
npm run e2e --prefix frontend -- --project=smoke
npm run e2e --prefix frontend -- --project=ssh
The projects start their own Vite server on 127.0.0.1:4173. Keep that port free
and run them in one Playwright invocation or sequentially. npm run e2e
--prefix frontend runs both projects together. The SSH project also needs
OpenSSH ssh and sftp on PATH and Paramiko from the test extra. It defaults to
the project .venv interpreter; set SSM_E2E_PYTHON to an explicit Python
executable when using a different test environment.
The smoke project uses controlled /api/v1 responses. It checks the six views,
read-only host details, literal remote-path encoding, file upload cancellation
and explicit overwrite, saved-directory CRUD, session/CSRF integration,
Back/Forward and rapid dialog reopening, empty workspaces, and desktop/narrow
Files layouts. Its screenshots are visual evidence of a local fixture only.
The ssh project runs tests/manual/modern_fixture.py: disposable Paramiko
SSH/SFTP servers and the actual Web backend, OpenSSH runner and SFTP client.
It uses temporary database/runtime/configuration paths, an in-memory test vault,
temporary keys and an explicit trusted known-hosts file. No user's database,
OS vault, SSH configuration or saved infrastructure is used. Forwarding is
restricted to the fixture's loopback ports; no interactive shell or PTY is
available. Fixed health/metadata probes are supported for the normal host checks.
| Acceptance area | Exercised behavior |
|---|---|
| Hosts and routes | Direct, two managed jumps, external jump alias, authentication failure, diagnosis, cached facts/fingerprint and batch checks |
| Credential test | A selected key authenticates against a host bound to a different key; the saved binding and host observations stay unchanged |
| Transfers | Routed SFTP metadata, download, conflict cancellation, explicit replacement, and publication of a new file |
| Large folders and fallbacks | 5,005 entries, filtering before the 5,000-entry cap, truncation/count hints, empty folders and nullable metadata |
| Limits | Disabled oversized downloads in the UI and a real backend size-limit refusal |
The backend integration tests independently check strict host-key verification, credential separation across hops, password/encrypted-key authentication, authentication without a remote session or command, cancellation/deadline cleanup, exact AskPass identity matching, idle jump forwarding, process-tree exit and stderr ownership, atomic publication and filesystem isolation. Rerun only those integrations with:
.venv/bin/python -m pytest tests/test_modern_fixture.py tests/test_credential_probe.py
Windows CI additionally runs unbound-jump, special-character-path, saved-trust and single-hop password/banner credential probes ten times with fresh isolated topologies and the same native client as the full suite. The runs alternate native proof reads with first-read observation. Failure diagnostics use the already-read proof data; additional log snapshots occur only after the actual proof check and classification. Any failed attempt stops the check; failed probes are never retried to obtain a passing result. Failure evidence contains only client version metadata, fixed stage flags, counts, authentication-method names and numeric file-access errors.
The loopback topology proves the application/client flows with a controlled server. It does not certify every remote SSH/SFTP implementation. In particular, servers without the required no-replace hard-link publication operation must return the explicit unsupported error covered by backend tests.
Clean package installations#
Build both distributions with the candidate version already set consistently:
.venv/bin/python -m pip install build
.venv/bin/python -m build --outdir dist .
.venv/bin/python scripts/packaging-smoke.py dist/ssh_server_manager-VERSION-py3-none-any.whl --version VERSION
.venv/bin/python scripts/packaging-smoke.py dist/ssh_server_manager-VERSION.tar.gz --version VERSION
Replace VERSION in filenames and arguments with the built package's version.
Remove obsolete distributions from the build output before selecting artifacts
for release or use an empty output directory.
packaging-smoke.py installs each distribution into a fresh virtual environment
outside the checkout. It confirms the installed package import, CLI version,
empty host listing, normal one-use Web launch, runtime contract, every manifest
asset, a CSRF-protected local tag write/delete, and Web status/stop cleanup.
This also verifies that a source checkout cannot mask omitted package data.
For the sdist, it also checks the public development inputs and rejects cached
dependencies or runtime state, then unpacks the source and collects its tests.
Only the two repository-level website test modules are excluded from that
collection; they still run in checkout CI, where the website sources exist.
All state is disposable, no SSH is requested, and ordinary metadata reads never
open the OS credential vault.
Upgrade from the published 0.8.0 wheel#
Run the migration smoke against the exact candidate wheel intended for release:
.venv/bin/python scripts/upgrade-smoke.py dist/ssh_server_manager-VERSION-py3-none-any.whl --version VERSION
This check needs package-index access. It downloads the actual published 0.8.0 wheel and installs it in a fresh virtual environment, then uses that installed package to create schema 5 with:
- Four hosts, explicit IDs, saved credentials of all three kinds, managed and external jump references, successful/failed/unrun check history.
- Unicode tags and reusable empty/assigned contexts, multiline notes, used and unused saved directories, and one Skill bound to two hosts.
- An enrolled Argon2 master-password hash, a fixture WebAuthn user/credential record, and arbitrary application settings.
It force-reinstalls the candidate wheel selected for the 0.9.0 release. Worker interpreters run in isolated mode and assert that the package comes from the temporary virtual environment. Every original column, row, ID, association and timestamp is compared exactly. The check also requires schema 7, correct last-success migration, nullable observation/key metadata defaults, initial credential revisions, empty new history tables, working master-password verification, SQLite integrity, and unchanged data after repeated startup.
Credentials are metadata fixtures: no real password/passphrase is put in a vault and no usable private key or passkey is seeded. Vault construction and network connection/process launches are denied inside the migration workers. This tests preservation of credential references and authentication settings without accessing the developer's OS keychain. Temporary environments and snapshots are removed on completion or failure.
CI scope and evidence#
.github/workflows/test.yml defines the following checks. A local pass is not
evidence that an unrun CI job passed; confirm each required check on the exact
commit being released.
| Job | Environment | Checks |
|---|---|---|
cross-platform |
macOS, Ubuntu Linux, Windows; Python 3.10 and 3.13 | Backend tests, API/type freshness, TypeScript/component checks, reproducible UI build, wheel and sdist installation |
| Upgrade smoke | macOS, Ubuntu Linux, Windows within cross-platform; Python 3.13 |
Published 0.8.0 schema 5 to candidate schema 7 |
browser-topology |
Ubuntu Linux; Python 3.13 | Six-view Chromium smoke and isolated real SSH/SFTP browser topology |
Browser tests use the normal one-use launch-file flow, with no authentication bypass route. The real SSH project's trace, screenshot and video capture are disabled because browser navigation initially contains the launch token. Its fixture output and readiness metadata do not contain that token. Do not enable request-body logging or attach private launch files, browser cookies, CSRF values, credential input, reveal results or test database snapshots to reports. Screenshots for documentation should use synthetic metadata and blank secret fields. Never use a real user's workspace to create release screenshots.