ha-deploy-kit
A small, reusable library for deploying a Home Assistant custom integration's code to a running HA host over SSH, and (optionally) wiring the HA-side restart notification. The core is Home-Assistant-free and standard-library only, so a deploy script can import it without pulling in Home Assistant; the HA adapter is a separate, explicitly-imported subpackage.
One process can deploy several integrations without cross-talk: all per-consumer
state lives in a frozen DeploySettings passed by argument — no module globals
and no os.environ mutation.
What it does
- Builds a content-hash inventory of your
custom_components/<domain>tree and compares it to the remote tree, deploying only the changed files. - Leaves remote-only files untouched (never deletes on the host).
- Streams files over SSH with an uncompressed, path-validated tar — no backups, no lock files, no rollback, no auto-restart.
- Optionally publishes a separately-served Lovelace card.
- Optionally raises a Home Assistant Repairs issue (via an HA service) telling the user a restart is needed. The engine never restarts HA itself; restart is opt-in and happens only when a user confirms a Repairs fix flow.
Install
Published on PyPI. Pin an exact version.
pip install "ha-deploy-kit==0.1.0"
The HA adapter needs Home Assistant, which is an optional extra:
pip install "ha-deploy-kit[ha]==0.1.0"
- Core requires Python 3.10+ and only the standard library (plus a CA
trust store:
certifi/truststore). - The
haadapter requires the Python version Home Assistant itself requires; install it in the HA runtime, not in your deploy tooling. - Home Assistant integrations must pin a versioned PyPI requirement
(
"ha-deploy-kit==0.1.0"inmanifest.json), never agit+…VCS URL — HA installs requirements in its own credential-less environment and treats URL requirements as never-satisfied, retrying every startup.
See docs/install.md for pyproject.toml, HA manifest.json,
and air-gapped host instructions, and docs/releasing.md for
how releases are published.
Public API
| Symbol | Import | Purpose |
|---|---|---|
DeploySettings |
ha_deploy_kit |
Frozen per-consumer settings (domain, source root, remote config, optional frontend, timeouts). |
FrontendAsset |
ha_deploy_kit |
Optional served-card mapping (source_rel, published_remote). |
DeployError |
ha_deploy_kit |
Raised for recoverable, sanitized deployment errors. |
run(settings, argv, ssh=None, environ=None) |
ha_deploy_kit.cli |
Orchestrate end to end: compare, report, publish the changed card, deploy the changed backend, notify. Honors a REMOTE_CONFIG env override. |
raise_restart_issue(base_url, token, settings) |
ha_deploy_kit.notify |
POST to the HA restart service; returns a sanitized status string. |
frontend_run(settings, plan, ssh, *, check) |
ha_deploy_kit.frontend |
Deploy the card into the tree and/or publish the served copy. |
ha.async_register_restart_service(hass, domain, ...) |
ha_deploy_kit.ha |
Idempotently register the restart service (empty schema). Pass fixable=True for a one-click Repairs card (pair with the fix flow). |
ha.async_raise_restart_issue(hass, domain, ...) |
ha_deploy_kit.ha |
Raise the deduplicated, non-persistent restart Repairs issue. |
ha.async_clear_restart_issue(hass, domain, ...) |
ha_deploy_kit.ha |
Clear the restart issue. |
ha.async_create_restart_fix_flow(hass, issue_id, ...) |
ha_deploy_kit.ha |
Optional opt-in confirm-and-restart flow (shows a confirmation dialog; restart only when the user submits it). |
ha.register_card_serving(hass, domain, lovelace_dir, card_url) |
ha_deploy_kit.ha |
Serve the card and register it as a frontend module. |
CLI
The package installs a ha-deploy console script. It deploys by default; pass
--check (or its alias --dry-run) to preview without writing anything.
ha-deploy # deploy changed backend files, then notify
ha-deploy --check # compare and report only; no writes, no notify
ha-deploy --root /path/to/your/repo
The source root is resolved from --root, then HA_DEPLOY_SOURCE_ROOT, then
the current directory. The domain is read from HA_DEPLOY_DOMAIN, or inferred
when the root has exactly one custom_components/<domain> subdirectory.
One-liner for your own deploy script
run is fully frontend-aware, so a consumer's deploy.py is just settings + a
call — no pipeline to copy. It publishes a changed card and deploys a changed
backend; a frontend-only change publishes the card but never notifies a restart.
import sys
from pathlib import Path
from ha_deploy_kit import DeploySettings, FrontendAsset
from ha_deploy_kit.cli import run
settings = DeploySettings(
domain="your_domain",
source_root=Path(__file__).resolve().parent,
frontend=FrontendAsset( # omit entirely for backend-only
source_rel="lovelace/your-card.js",
published_remote="www/your_domain/your-card.js",
),
)
raise SystemExit(run(settings, sys.argv[1:]))
TARGET/HA_BASE_URL/HA_TOKEN and an optional REMOTE_CONFIG override come
from .env or the process environment; run applies REMOTE_CONFIG for you.
See docs/configuration.md for settings and .env, and
docs/adopting.md for a step-by-step adoption guide.
Design notes
This library deliberately differs from typical hand-rolled HA deploy scripts. Each choice below is intentional:
- No positional target and no
--restart/--no-restart/--card-onlyflags. The target comes from the environment; the engine never restarts HA, so there is no restart flag to pass. - The engine never executes a restart. It can only raise a Repairs issue; the actual restart is opt-in behind a user-confirmed fix flow.
- A frontend-only change never notifies a backend restart. Backend and frontend are separate concerns.
- Structured remote inventory. The remote probe returns an explicit four-field result (hashes, stale files, a missing-tree flag, published hash) rather than guessing from text.
- Environment precedence is explicit. Process environment overrides
.env; the library never mutatesos.environ. - Path validation. Tar members are rejected if absolute, containing
.., NUL, or newlines, or escaping the component directory. - No backups, lock files, rollback, or auto-restart, and tokens, raw bodies, and secret-bearing errors are never printed. TLS and SSH verification are never weakened.
- Real CA trust store for the notification. The HTTPS call to Home Assistant
verifies against the OS trust store via
truststore(matchingcurl), falling back tocertifithen the stdlib default — all strictly verifying (CERT_REQUIRED+ hostname check). This avoids misreporting a valid cert astls-erroron platforms whose default context has no usable CA store (macOS, python.org, fresh venvs);tls-erroris reserved for genuine failures.
Releases
Releases are automated: every merge to main runs semantic-release, which picks
the next version from the Conventional Commit messages (fix: → patch,
feat: → minor, feat!:/BREAKING CHANGE: → major; docs:/chore:/test:
publish nothing) and publishes to PyPI via Trusted Publishing. Write PR
titles/commits in that form. See docs/releasing.md for the
one-time setup and the manual fallback.
Metadata
Release files for ha-deploy-kit 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ha_deploy_kit-0.1.3.tar.gz | 44.7 kB | With provenance | Details for ha_deploy_kit-0.1.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ha_deploy_kit-0.1.3-py3-none-any.whl | Python 3 | none | any | Details for ha_deploy_kit-0.1.3-py3-none-any.whl |
Total release size: 68.7 kB
Release files / ha_deploy_kit-0.1.3.tar.gz
| Download URL | ha_deploy_kit-0.1.3.tar.gz |
|---|---|
| Size | 44.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c1359ce7da54cbd998f05c96a69c7c4119561b76f9a2efa9208f38214af13b63
|
|
BLAKE2b-256 checksum How to use checksums |
96133afc7f8e7818150c537a2ac617d465a80cf0b245b71dcd5f873b4fa7e4ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logRelease files / ha_deploy_kit-0.1.3-py3-none-any.whl
| Download URL | ha_deploy_kit-0.1.3-py3-none-any.whl |
|---|---|
| Size | 24.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c25a6195086643e91c100a2225822fd7d9dd287f2f2c50e2352568d5d8f272e2
|
|
BLAKE2b-256 checksum How to use checksums |
af4f7ff61e36bb79849bdf31d17635f0a32d9cf8786a0fdbce9f175d9a30fc69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logSecurity for ha-deploy-kit 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for ha_deploy_kit-0.1.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.