Metadata-Version: 2.5
Name: ha-deploy-kit
Version: 0.1.3
Summary: HA-free SSH deploy / compare / notify / optional-frontend toolkit for Home Assistant custom components
Project-URL: Homepage, https://github.com/latrekc-meta/ha-deploy-kit
Project-URL: Repository, https://github.com/latrekc-meta/ha-deploy-kit
Project-URL: Issues, https://github.com/latrekc-meta/ha-deploy-kit/issues
Author: latrekc-meta
License: MIT License
        
        Copyright (c) 2026 latrekc-meta
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: custom-component,deploy,home-assistant,homeassistant,lovelace,ssh
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Home Automation
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.10
Requires-Dist: certifi
Requires-Dist: truststore>=0.9
Provides-Extra: ha
Requires-Dist: homeassistant; extra == 'ha'
Description-Content-Type: text/markdown

# 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.

```sh
pip install "ha-deploy-kit==0.1.0"
```

The HA adapter needs Home Assistant, which is an optional extra:

```sh
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 `ha` adapter** 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"` in `manifest.json`), **never** a `git+…` 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](docs/install.md) for `pyproject.toml`, HA `manifest.json`,
and air-gapped host instructions, and [docs/releasing.md](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.

```sh
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.

```python
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](docs/configuration.md) for settings and `.env`, and
[docs/adopting.md](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-only`
  flags.** 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 mutates `os.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` (matching `curl`), falling
  back to `certifi` then the stdlib default — all strictly verifying
  (`CERT_REQUIRED` + hostname check). This avoids misreporting a valid cert as
  `tls-error` on platforms whose default context has no usable CA store (macOS,
  python.org, fresh venvs); `tls-error` is 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](docs/releasing.md) for the
one-time setup and the manual fallback.

[Conventional Commit]: https://www.conventionalcommits.org/
